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

# Orders

> Reference for the OrdersService class in the Tybrite SDK.

The `OrdersService` class (accessed via `client.orders`) manages the entire commerce lifecycle. All order write operations require mandatory **HMAC Security** to ensure complete data integrity.

<Note>
  **Sandbox isolation:** Orders created with a `tybrite_sk_test_*` key are stored in the sandbox environment and are never visible to production keys, and vice versa. Every response includes a `Tybrite-Environment: sandbox | production` header confirming which environment your key resolved to.
</Note>

## 🔐 Security & Signing (HMAC)

To prevent tampering, replay attacks, and duplicate processing of updates, `createOrder` and `updateOrder` require both an HMAC-SHA256 signature and a mandatory Idempotency Key.

### Signing Process

1. **Generate Timestamp**: Get the current Unix timestamp in seconds.
2. **Prepare Payload**: Concatenate the timestamp and the JSON request body with a dot: `timestamp + "." + JSON_body`.
3. **Generate Signature**: Compute an HMAC-SHA256 signature of the payload using your **HMAC Secret** (found in Integrations → Developer).
4. **Base64 Encode**: The resulting signature must be Base64 encoded.

### Code Implementation (TypeScript/Node.js)

```typescript theme={null}
import crypto from 'crypto';

// 1. Define Signing Helper
function generateHmacSignature(payload: string, secret: string): string {
  return crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('base64');
}

// 2. Prepare Data & Sign
const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify(orderData);
const payload = `${timestamp}.${body}`;
const signature = generateHmacSignature(payload, hmacSecret);

// 3. Execute request with headers
const order = await client.orders.createOrder({
  idempotencyKey: `order-${timestamp}`,
  xTimestamp: timestamp,
  xSignature: signature,
  requestBody: orderData
});
```

<Warning>
  **NEVER expose your HMAC Secret client-side.** Signing should always occur on a secure backend server. Requests with missing or invalid signatures will return `401 Unauthorized`.
</Warning>

***

## Core Operations

### `listOrders`

Retrieve a paginated list of your store's orders, newest first. Use this to build order dashboards, fulfillment queues, or customer order history — anywhere you need to browse orders without already knowing an order ID.

The list returns order summaries **without line items** (to keep responses fast). When you need the full order including its items, call [`getOrder`](#getorder) with the order's `id`.

<Warning>
  Reading orders requires a **Secret Key**. Publishable keys receive `403 Forbidden`. Order data contains customer details, addresses, and payment status — it must never be exposed to browsers.
</Warning>

**Parameters** (all optional):

| Parameter       | Type   | Description                                                                                       |
| :-------------- | :----- | :------------------------------------------------------------------------------------------------ |
| `limit`         | number | How many orders to return per page (1–200, default 50).                                           |
| `cursor`        | string | Pass the `next_cursor` from a previous response to fetch the next page.                           |
| `paymentStatus` | string | Filter by payment status: `pending`, `paid`, `failed`, or `refunded`.                             |
| `orderStatus`   | string | Filter by fulfillment status: `pending`, `processing`, `shipped`, `delivered`, or `cancelled`.    |
| `customerId`    | string | Return only orders belonging to a specific customer.                                              |
| `fields`        | string | Comma-separated list of fields to return per order (e.g. `order_number,total_amount,created_at`). |

```typescript theme={null}
const client = new Tybrite({ apiKey: process.env.TYBRITE_SECRET_KEY });

// First page — most recent 20 paid orders
const page = await client.orders.listOrders({
  limit: 20,
  paymentStatus: 'paid',
});

console.log(page.orders);            // array of order summaries
console.log(page.pagination.has_more); // true if more pages exist
```

**Paging through all results** — keep calling with the returned cursor until `has_more` is `false`:

```typescript theme={null}
let cursor: string | undefined = undefined;

do {
  const page = await client.orders.listOrders({ limit: 50, cursor });
  for (const order of page.orders ?? []) {
    // process each order
  }
  cursor = page.pagination?.next_cursor ?? undefined;
} while (cursor);
```

**Response shape:**

```typescript theme={null}
{
  orders: [
    {
      id: 'order-uuid',
      order_number: 'ORD-1771523999910',
      customer_name: 'John Doe',
      payment_status: 'paid',
      order_status: 'processing',
      total_amount: 2520,
      environment: 'production',
      created_at: '2026-02-19T10:30:00Z'
      // ...other order header fields (no `items`)
    }
  ],
  pagination: {
    limit: 50,
    next_cursor: 'eyJ...' | null, // pass this back as `cursor` for the next page
    has_more: false
  }
}
```

***

### `reserveStock`

**Optional.** Holds stock for the items a customer is checking out so they can't be sold to someone else while payment is in flight. Returns `201` with `reservation_ids` and an expiry.

Use it when there's a gap between "customer commits to buy" and "payment confirms" — card redirects, 3-D Secure, mobile-money prompts — so the stock is claimed *before* the customer pays. For instant-capture or synchronous flows you can skip it and call `createOrder` directly; Galactic Core still prevents stock from going negative at order time. Reserving is a stronger guarantee for async payments, **not** a mandatory step before every order.

Reservations are all-or-nothing: if any item lacks available stock, nothing is reserved and you get `409 insufficient_stock`. Each hold expires automatically (15 minutes by default); an abandoned checkout simply lets the hold lapse — no explicit release needed. Works with publishable or secret keys.

```typescript theme={null}
const { reservation_ids, expires_in_seconds } = await client.orders.reserveStock({
  requestBody: {
    items: [
      { variant_id: 'variant-uuid', quantity: 2 }
    ],
    // optional: ttl_seconds, customer_id, session_id (for checkout recovery)
  }
});

// ...customer completes payment...

// Convert the hold into the sale by passing reservation_ids to createOrder:
const result = await client.orders.createOrder({
  idempotencyKey: `order-${Date.now()}`,
  xTimestamp: Math.floor(Date.now() / 1000),
  xSignature: hmacSignature,
  requestBody: {
    total_amount: 5000,
    payment_method: 'card',
    payment_status: 'paid',
    items: [{ product_id: 'product-uuid', variant_id: 'variant-uuid', quantity: 2, unit_price: 2500 }],
    reservation_ids, // converts the hold into the actual stock deduction
  }
});
// → { order: { id, order_number, payment_status: 'paid', total_amount, … }, post_processing_warnings? }
```

The `reserveStock` call itself returns:

```json Response theme={null}
{
  "reservations": [
    { "reservation_id": "a81b3a3d-f810-47f4-ac50-592c3f22482f", "variant_id": "variant-uuid", "quantity": 1 }
  ],
  "reservation_ids": ["a81b3a3d-f810-47f4-ac50-592c3f22482f"],
  "expires_in_seconds": 900
}
```

***

### `createOrder`

Place a new order. Returns **`201 Created`** (semantic create) with the new order resource. Requires verified signatures and idempotency protection.

* **Required body fields**: `customer_email`, `customer_name`, `billing_address`, `shipping_address`, `subtotal`, `total_amount`, `payment_method`, and a non-empty `items` array. Each item must include **`variant_id` or `product_id`** (at least one).
* **`variant_id` vs `product_id` per item** — prefer **`variant_id`** (the exact variant the shopper chose; pass the same one you put in the cart straight through). If you send only `product_id`, the order is placed against the product's **default** variant — fine for single-variant products, but for a multi-variant product it may not be the one the shopper selected. Sending both is fine: `variant_id` wins and `product_id` is resolved from it. See [Products and variants — which ID to send where](/concepts#products-and-variants) for the full picture.
* **Optional**: `customer_id` (guest checkout is supported — pass customer details without a `customer_id`), `customer_phone`, `product_name` per item (resolved from the product if omitted), `tax_amount`, `shipping_amount`, `discount_amount`, `notes`, `gift_card_redemption`, `promotion_usages`, and `reservation_ids` (from `reserveStock` — converts a checkout hold into the stock deduction instead of a fresh decrement).
* **Tax**: if you omit `tax_amount` and the store has automatic tax enabled, Galactic Core calculates jurisdiction-accurate tax for the `shipping_address` and returns it on the order as `tax_amount`, with a per-jurisdiction `tax_breakdown` array and a `tax_source` of `automatic`. If automatic calculation is unavailable it falls back to the store's configured rate (`tax_source: fallback`) so the order never fails on tax. If you pass your own `tax_amount` (e.g. you calculated tax elsewhere), it is honored as-is. See [Order schema](#response-shape) for the fields.
* **Shipping**: `shipping_amount` is validated server-side (like prices and tax) so a tampered value is rejected. For a zone/distance store, pass `shipping_latitude` + `shipping_longitude` and the amount is checked against the resolved delivery fee. For a multi-carrier (Shippo) store, pass the chosen `shippo_rate_id` (from a `calculateShipping` quote's `rates[]`) and the amount must match that real carrier quote. A mismatch returns `400 price_mismatch`. (When shipping can't be resolved server-side, the value is accepted and marked unverified — a sale never fails on shipping.)
* **Store credit**: pass `apply_store_credit: true` (optionally with `store_credit_amount` to cap it) to spend the customer's redeemable store-credit balance against this order. Requires a `customer_id`. The amount actually applied — capped at the order total and the available balance — is returned as `store_credit_applied`.
* **Ad conversion tracking (optional)**: if the shopper arrived from a paid ad, pass the click id — **`gclid`** (Google) and/or **`fbclid`** (Meta / Facebook & Instagram) — and their privacy-consent state as **`ad_consent: { ad_user_data, ad_personalization, jurisdiction }`** (each consent value `'granted' | 'denied' | 'unspecified'`; `jurisdiction` is the shopper's region, e.g. `EEA`/`UK`/`CH`/`US`). On a paid order this lets the merchant's advertising get credit for the sale on each platform. Galactic Core records the consent state with the order and only reports the conversion to the ad platform where the shopper's consent and region allow it — so always forward the true captured values (never guess). The Meta conversion is sent server-side and de-duplicated against the storefront's Meta Pixel using the order id, so it counts even when the browser blocks the Pixel. Storefront generators should wire this alongside a consent banner for EEA, UK, and Swiss visitors.
* Missing a required field returns `400` with a message naming the field.
* **Replay Protection**: The `X-Timestamp` must be within **5 minutes** of the server time.
* **Post-processing**: When `payment_status: "paid"` is set, Galactic Core applies gift card redemption, stock reduction, and customer metrics updates. See [Post-Processing Warnings](#post-processing-warnings) below — the response may include a `post_processing_warnings` array that you **must** check.

```typescript theme={null}
const result = await client.orders.createOrder({
  idempotencyKey: `order-${Date.now()}-abc123`,
  xTimestamp: Math.floor(Date.now() / 1000),
  xSignature: hmacSignature, // base64 HMAC-SHA256 of `${timestamp}.${body}`
  requestBody: {
    customer_email: 'jane@example.com',
    customer_name: 'Jane Doe',
    billing_address: { street: '123 Main St', city: 'New York', state: 'NY', zip: '10001', country: 'US' },
    shipping_address: { street: '123 Main St', city: 'New York', state: 'NY', zip: '10001', country: 'US' },
    subtotal: 5000,
    total_amount: 5000,
    payment_method: 'card',
    customer_id: 'optional-customer-uuid', // optional; omit for guest checkout
    items: [
      // Prefer variant_id — the exact SKU the shopper chose (e.g. straight from the cart).
      { variant_id: 'variant-uuid', quantity: 2, unit_price: 2500, total_price: 5000 }
      // …or send product_id alone to use the product's default variant.
    ],
    payment_status: 'paid' // triggers inventory + accounting + metrics
  }
});
const order = result.order;
// order.id is the new order UUID
// result.post_processing_warnings is undefined OR present with details
```

```json Response theme={null}
{
  "order": {
    "id": "0468af54-5f6b-4be5-a4e6-3aa7a79450ba",
    "order_number": "ORD-1781955469360",
    "customer_id": "d6f46dad-189f-489a-a1a9-6d63cf103fbe",
    "customer_email": "jane.doe@example.com",
    "customer_name": "Jane Doe",
    "subtotal": 598,
    "tax_amount": 53.08,
    "tax_source": "automatic",
    "tax_breakdown": [
      { "country": "US", "region": "NY", "jurisType": "State", "jurisName": "NEW YORK", "taxName": "NY STATE TAX", "rate": 0.04, "taxable": 598, "tax": 23.92 },
      { "country": "US", "region": "NY", "jurisType": "City", "jurisName": "NEW YORK CITY", "taxName": "NY CITY TAX", "rate": 0.045, "taxable": 598, "tax": 26.91 },
      { "country": "US", "region": "NY", "jurisType": "Special", "jurisName": "METROPOLITAN COMMUTER TRANSPORTATION DISTRICT", "taxName": "NY SPECIAL TAX", "rate": 0.00375, "taxable": 598, "tax": 2.25 }
    ],
    "shipping_amount": 5.99,
    "discount_amount": 0,
    "total_amount": 657.07,
    "payment_method": "card",
    "payment_status": "paid",
    "order_status": "pending",
    "created_at": "2026-06-20T11:37:50.273614+00:00",
    "items": [
      {
        "id": "d72a3cbe-2232-4e84-a25c-fe1245c5e5e0",
        "product_id": "product-uuid",
        "product_name": "Samsung Galaxy Watch 6",
        "quantity": 2,
        "unit_price": 299,
        "total_price": 598
      }
    ]
  }
}
```

<Tip>
  Because you pass an `idempotencyKey`, the SDK can safely [auto-retry](/sdk/introduction#automatic-retries) this call on a transient failure — a retry returns the original order rather than creating a duplicate.
</Tip>

<Note>
  `createOrder` returns `{ order, store_credit_applied?, post_processing_warnings? }`. `store_credit_applied` is present only when store credit was applied. `getOrder`, `updateOrder`, and `listOrders` return the order resource(s) at the top level.
</Note>

***

## Post-Processing Warnings

When an order is created with `payment_status: "paid"`, Galactic Core applies these follow-up steps:

1. **Gift card redemption** (if `gift_card_redemption` provided)
2. **Stock reduction** per item
3. **Customer metrics** update (purchase count and lifetime value)

If any step fails, the order is still recorded — but the response includes a `post_processing_warnings` array. **You MUST check this array** and route any failures to a support or retry flow.

### Response shape

```typescript theme={null}
{
  order: {
    id: 'order-uuid',
    // ... full order fields
  },
  post_processing_warnings: [
    {
      stage: 'gift_card_redemption',
      message: 'Gift card redemption failed: insufficient balance. The order was created but no gift card credit was applied. Please contact support.'
    }
    // OR
    {
      stage: 'stock_reduction',
      message: 'Some inventory updates failed. Order is recorded; an operator will reconcile stock manually.'
    }
  ]
}
```

### Handling pattern

```typescript theme={null}
const result = await client.orders.createOrder({ /* ... */ });
const order = result.order;

if (result.post_processing_warnings?.length) {
  for (const warning of result.post_processing_warnings) {
    if (warning.stage === 'gift_card_redemption') {
      // Alert the customer + create a support ticket
      // The customer paid full price but the gift card credit was not applied
      await flagForRetry(order.id, warning);
    }
    if (warning.stage === 'stock_reduction') {
      // Notify ops; inventory needs manual review
      console.warn('Stock review needed for order', order.id);
    }
  }
}
```

<Note>
  Customer metrics (`total_purchases`, lifetime value) update automatically when an order is paid. Counts stay accurate even when many orders for the same customer are placed at once — no action needed on your side.
</Note>

***

### `getOrder`

Retrieve complete details for a specific order.

<Warning>
  Reading orders requires a **Secret Key**. Publishable keys receive `403 Forbidden`. Order data contains customer PII, addresses, and payment status — it must not be exposed to browsers.
</Warning>

```typescript theme={null}
const client = new Tybrite({ apiKey: process.env.TYBRITE_SECRET_KEY });
const order = await client.orders.getOrder({ id: 'order-uuid' });
```

```json Response theme={null}
{
  "id": "0468af54-5f6b-4be5-a4e6-3aa7a79450ba",
  "order_number": "ORD-1781955469360",
  "customer_id": "d6f46dad-189f-489a-a1a9-6d63cf103fbe",
  "customer_email": "john.doe@example.com",
  "customer_phone": "+12125550142",
  "customer_name": "John Doe",
  "billing_address": { "street": "350 Fifth Avenue", "city": "New York", "state": "NY", "zip": "10118", "country": "US" },
  "shipping_address": { "street": "350 Fifth Avenue", "city": "New York", "state": "NY", "zip": "10118", "country": "US" },
  "subtotal": 598,
  "tax_amount": 53.08,
  "tax_source": "automatic",
  "tax_breakdown": [
    { "country": "US", "region": "NY", "jurisType": "State", "jurisName": "NEW YORK", "taxName": "NY STATE TAX", "rate": 0.04, "taxable": 598, "tax": 23.92 },
    { "country": "US", "region": "NY", "jurisType": "City", "jurisName": "NEW YORK CITY", "taxName": "NY CITY TAX", "rate": 0.045, "taxable": 598, "tax": 26.91 },
    { "country": "US", "region": "NY", "jurisType": "Special", "jurisName": "METROPOLITAN COMMUTER TRANSPORTATION DISTRICT", "taxName": "NY SPECIAL TAX", "rate": 0.00375, "taxable": 598, "tax": 2.25 }
  ],
  "shipping_amount": 5.99,
  "discount_amount": 0,
  "total_amount": 657.07,
  "payment_method": "card",
  "payment_status": "paid",
  "order_status": "pending",
  "notes": "Leave with front desk",
  "tracking_number": null,
  "created_at": "2026-06-20T11:37:50.273614+00:00",
  "updated_at": "2026-06-20T11:37:50.273614+00:00",
  "items": [
    {
      "id": "d72a3cbe-2232-4e84-a25c-fe1245c5e5e0",
      "order_id": "0468af54-5f6b-4be5-a4e6-3aa7a79450ba",
      "product_id": "0186665f-f0a1-4c79-976a-919b5b1c2daa",
      "product_name": "Samsung Galaxy Watch 6",
      "product_sku": "SAM-WATCH-6-44-0186",
      "quantity": 2,
      "unit_price": 299,
      "total_price": 598,
      "product_options": null
    }
  ]
}
```

***

### `updateOrder`

Update fulfillment or payment status. **Requires HMAC signature and Idempotency Key.**

* **Idempotency**: Use a unique key for each distinct update (e.g., `update-payment-{order_id}-{timestamp}`).
* **Protection**: This prevents double-triggering side effects like accounting entries or inventory reduction on network retries — and lets the SDK [auto-retry](/sdk/introduction#automatic-retries) the call safely.

```typescript theme={null}
const updated = await client.orders.updateOrder({
  id: 'order-uuid-here',
  idempotencyKey: `update-status-${orderId}-${Date.now()}`,
  xTimestamp: Math.floor(Date.now() / 1000),
  xSignature: '...',
  requestBody: {
    payment_status: 'paid', // Triggers auto-accounting & inventory reduction
    order_status: 'shipped',
    tracking_number: 'CARGO-123-ABC'
  }
});
```

The full updated order is returned at the top level:

```json Response theme={null}
{
  "id": "0468af54-5f6b-4be5-a4e6-3aa7a79450ba",
  "order_number": "ORD-1781955469360",
  "payment_status": "paid",
  "order_status": "shipped",
  "tracking_number": "1Z999AA10123456784",
  "shipped_at": "2026-06-20T11:37:42.557+00:00",
  "delivered_at": null,
  "total_amount": 657.07,
  "updated_at": "2026-06-20T11:37:50.273614+00:00"
}
```

## Security & Error Handling

| Error Code         | Meaning                | Resolution                                         |
| :----------------- | :--------------------- | :------------------------------------------------- |
| `401 Unauthorized` | Invalid HMAC signature | Verify your secret key and payload concatenation.  |
| `401 Unauthorized` | Missing X-Timestamp    | Ensure the `X-Timestamp` header is sent.           |
| `401 Unauthorized` | Expired Timestamp      | Ensure your server clock is synced (5-min window). |
| `403 Forbidden`    | Publishable Key Used   | Create orders using a **Secret Key** only.         |

## Side Effects of `payment_status: 'paid'`

Transitioning an order to the **paid** state automatically triggers:

1. **Inventory Sync**: Live stock reduction across variants.
2. **Accounting Engine**: Double-entry bookkeeping entry creation.
3. **LTV Update**: Increments customer lifetime value and purchase counts (accurate even under heavy concurrent order load).
4. **Promoter Logic**: Marks loyalty points and promotion usage as finalized.

***

### Response Codes

| Code  | Endpoint                                                         | Meaning                                                                                                                                                                                                    |
| :---- | :--------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | `GET /v1/orders`, `GET /v1/orders/{id}`, `PATCH /v1/orders/{id}` | Success. `GET /v1/orders` returns a paginated list; `PATCH` returns the updated order.                                                                                                                     |
| `201` | `POST /v1/orders`                                                | Order created. Response includes `post_processing_warnings?` if any post-paid step failed.                                                                                                                 |
| `400` | All                                                              | Invalid request — bad pagination/filter value, missing `total_amount` / `payment_method` / `items`, an item with neither `variant_id` nor `product_id`, or invalid tax/subtotal for a tax-exclusive store. |
| `401` | All                                                              | Invalid API key, or invalid / expired HMAC signature.                                                                                                                                                      |
| `403` | Read & write                                                     | Publishable key used on an order endpoint. Orders are secret-key only.                                                                                                                                     |
| `404` | `GET /v1/orders/{id}`, `PATCH`, `POST`                           | Order not found, or a `product_id` referenced in `items` not found.                                                                                                                                        |
| `409` | `POST /v1/orders`                                                | Idempotency key reused with a different payload.                                                                                                                                                           |
| `429` | All                                                              | Rate limit exceeded.                                                                                                                                                                                       |
| `500` | All                                                              | Server error.                                                                                                                                                                                              |
