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

# Orders

> Create, update, and query orders with powerful filtering, property-based queries, and automatic merging

# Orders

This guide covers the complete order lifecycle: creating orders with buyer and item references, updating order state and properties, and querying with rich filters including JSONB property searches.

<Info>
  All endpoints require header-based auth: `X-Business-ID` and `X-API-Key`.
</Info>

## Authentication

Include these headers in every request:

* `X-Business-ID`: Your business identifier
* `X-API-Key`: Your API key

## Overview: Orders, Buyers, and Items

Orders in Polinate connect buyers with items:

1. **Create Items** with variants (SKU, price, dimensions, etc.)
2. **Create Buyers** representing your customers/partners
3. **Create Orders** that reference the buyer and specific item variants

Each order contains:

* A reference to the buyer (`partnershipId`, `membershipId`)
* Order-level metadata (delivery address, dates, totals, custom properties)
* **Order items** that reference specific item variants with quantity and pricing

The API returns full context so you always know which buyer and which items/variants are in each order.

## List Orders

Endpoint: `GET /api/v1/orders`

Supports pagination, time-window filters, state filters, and property-based queries:

**Pagination:**

* `page` (number, default 1)
* `limit` (number, max 100, default 20)

**Time-window filters:**

* `createdSince` (ISO datetime): orders with `createdAt > createdSince`
* `updatedSince` (ISO datetime): orders with `updatedAt > updatedSince`
* `modifiedSince` (ISO datetime): orders where (`createdAt > ts` OR `updatedAt > ts`)
  * <strong>Mutual exclusivity:</strong> `modifiedSince` cannot be combined with `createdSince` or `updatedSince`. You may combine `createdSince` and `updatedSince` together.

**State filters:**

* `phase` (enum): High-level order state
* `status` (enum): Physical fulfillment/movement state

**Property-based queries:**

* `propertyPath` (string): Dot-notation path to a property (e.g., `sage200Evo.salesOrderNo`)
* `propertyValue` (string): Filter orders where the property equals this value
* `propertyExists` (boolean): Filter by property existence (`true`) or absence (`false`)
  * <strong>Mutual exclusivity:</strong> `propertyValue` and `propertyExists` cannot be used together
  * Both require `propertyPath` to be specified

Results are scoped to your business and exclude soft-deleted orders.

### Understanding Phase vs Status

Orders have two complementary state dimensions:

**Phase** represents the <strong>high-level lifecycle</strong> of an order:

* `DRAFT`: Order is being prepared
* `PENDING`: Order is awaiting acceptance/confirmation
* `ACTIVE`: Order is confirmed and in progress
* `ARCHIVED`: Order is complete and archived
* `REJECTED`: Order was declined
* `CANCELLED`: Order was cancelled
* `DELETED`: Order was soft-deleted

**Status** represents the <strong>physical movement/fulfillment state</strong>:

* `UNASSIGNED`: Not yet assigned for processing
* `ACCEPTED`: Accepted and ready for fulfillment
* `PREPARING`: Being prepared for shipment
* `SHIPPED`: In transit to destination
* `DELIVERED`: Successfully delivered
* `CANCELLED`: Fulfillment cancelled
* `RETURNED`: Returned by recipient

<Info>
  Phase and status work together. For example, an order can have `phase: CANCELLED` but `status: SHIPPED` if it was cancelled after already being dispatched.
</Info>

### Example Queries

<CodeGroup>
  ```bash cURL (Basic list) theme={null}
  curl -G "https://integrations.polinate.ai/api/v1/orders" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}" \
    --data-urlencode "page=1" \
    --data-urlencode "limit=20"
  ```

  ```bash cURL (Time-window filter) theme={null}
  curl -G "https://integrations.polinate.ai/api/v1/orders" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}" \
    --data-urlencode "modifiedSince=2025-02-01T00:00:00Z" \
    --data-urlencode "phase=PENDING" \
    --data-urlencode "status=UNASSIGNED"
  ```

  ```bash cURL (Property exists filter) theme={null}
  # Find orders that have been synced to an external system
  curl -G "https://integrations.polinate.ai/api/v1/orders" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}" \
    --data-urlencode "propertyPath=externalSystem.orderId" \
    --data-urlencode "propertyExists=true"
  ```

  ```bash cURL (Property NOT exists filter) theme={null}
  # Find orders that need to be synced to external system
  curl -G "https://integrations.polinate.ai/api/v1/orders" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}" \
    --data-urlencode "phase=PENDING" \
    --data-urlencode "status=UNASSIGNED" \
    --data-urlencode "propertyPath=sage200Evo.salesOrderNo" \
    --data-urlencode "propertyExists=false"
  ```

  ```bash cURL (Property value filter) theme={null}
  # Find orders with specific external reference
  curl -G "https://integrations.polinate.ai/api/v1/orders" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}" \
    --data-urlencode "propertyPath=externalSystem.orderId" \
    --data-urlencode "propertyValue=12345"
  ```
</CodeGroup>

<Note>
  **Best practices:**

  * Use `modifiedSince` for catch-up syncs: it returns all orders created or updated after the given timestamp
  * Use `propertyExists=false` to find orders that need processing (e.g., not yet synced to your ERP)
  * Combine state filters with property filters for powerful queries
</Note>

### Response Structure

Each order includes buyer context and full item details:

```json theme={null}
{
  "items": [
    {
      "id": "ord_abc123",
      "partnershipId": "pship_xyz789",  // Identifies the buyer/customer
      "membershipId": "mem_def456",      // Optional: specific member who created the order
      "integrationId": null,
      "phase": "PENDING",
      "status": "UNASSIGNED",
      "internalReference": "ORD-1001",   // Your internal order number
      "externalReference": "PO-2025-001", // Buyer's PO number
      "deliveryAddress": "123 Main St, Sydney NSW 2000",
      "expectedDeliveryAt": "2025-03-15T09:00:00Z",
      "currency": "AUD",
      "totalAmount": "250.00",
      "properties": {
        "sage200Evo": {
          "salesOrderNo": 12345,
          "customField": "value"
        }
      },
      "createdAt": "2025-02-15T12:00:00Z",
      "updatedAt": "2025-02-16T08:30:00Z",
      "orderItems": [
        {
          "id": "oi_1",
          "orderId": "ord_abc123",
          "lineNumber": 0,
          "itemListEntryId": "ile_xyz",
          "itemListEntry": {
            "itemVariantId": "var_123",     // The variant being ordered
            "itemVariant": {
              "itemId": "item_456"          // The parent item
            }
          },
          "quantity": 10,
          "unitOfMeasure": "Each",
          "currency": "AUD",
          "price": "25.00",
          "cost": "15.00",
          "properties": null,
          "createdAt": "2025-02-15T12:00:00Z",
          "updatedAt": "2025-02-15T12:00:00Z"
        }
      ]
    }
  ],
  "total": 42,
  "page": 1,
  "pageSize": 20
}
```

**Key fields explained:**

* `partnershipId`: Links to the buyer (see Buyers API)
* `orderItems[].itemListEntry.itemVariantId`: The specific variant ordered
* `orderItems[].itemListEntry.itemVariant.itemId`: The parent item
* `properties`: Flexible JSONB field for custom data (see Properties section below)

## Get Individual Order

Endpoint: `GET /api/v1/orders/{id}`

Fetch a single order by ID with all buyer and item context:

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://integrations.polinate.ai/api/v1/orders/ord_abc123" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}"
  ```
</CodeGroup>

Returns the same structure as the list endpoint, but for a single order.

## Update an Order

Endpoint: `PATCH /api/v1/orders/{id}`

Update order state, metadata, and properties. The API supports **partial updates** with automatic property merging.

<CodeGroup>
  ```bash cURL (Update state) theme={null}
  curl -X PATCH "https://integrations.polinate.ai/api/v1/orders/ord_abc123" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}" \
    -H "Content-Type: application/json" \
    -d '{
      "order": {
        "phase": "ACTIVE",
        "status": "ACCEPTED",
        "notes": "Confirmed and ready for fulfillment"
      }
    }'
  ```

  ```bash cURL (Update properties - automatic merge) theme={null}
  curl -X PATCH "https://integrations.polinate.ai/api/v1/orders/ord_abc123" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}" \
    -H "Content-Type: application/json" \
    -d '{
      "order": {
        "properties": {
          "sage200Evo": {
            "salesOrderNo": 12345,
            "invoiceNo": 67890
          }
        }
      }
    }'
  ```
</CodeGroup>

**Returns**: Full updated order with all items (same structure as GET)

## Ingest an Order (Idempotent)

Endpoint: `POST /api/v1/orders`

Use an `Idempotency-Key` header so retries don't create duplicates. The expected key is:

```
sha256("{orderId}-{businessId}-order-integration")
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://integrations.polinate.ai/api/v1/orders" \
    -H "X-Business-ID: {{businessId}}" \
    -H "X-API-Key: {{apiKey}}" \
    -H "Idempotency-Key: {{sha256(orderId-businessId-order-integration)}}" \
    -H "Content-Type: application/json" \
    -d '{
      "orderId": "ext-ord-123",
      "businessId": "{{businessId}}"
    }'
  ```

  ```json Successful Response theme={null}
  { "success": true }
  ```

  ```json 400 Invalid Idempotency Key theme={null}
  { "error": "Invalid idempotency key format" }
  ```
</CodeGroup>

## Working with Properties

The `properties` field is a flexible JSONB column that lets you store custom data with orders. This is perfect for:

* External system references (ERP order numbers, tracking IDs)
* Integration metadata (sync status, timestamps)
* Custom business logic data

### Deep Merge Behavior

When you update properties via PATCH, the API performs a **deep merge** (upsert behavior):

* **Top-level keys** are merged independently
* **Nested objects** within the same key are merged
* **New keys** are added without affecting existing keys
* **Null values** are stored as-is (use `null` to explicitly set a property to null)

### Property Merge Examples

**Initial order properties:**

```json theme={null}
{
  "properties": {
    "unleashed": {
      "orderId": "ABC-123",
      "syncedAt": "2025-01-15T10:00:00Z"
    },
    "customField": "value"
  }
}
```

**PATCH request:**

```json theme={null}
{
  "order": {
    "properties": {
      "sage200Evo": {
        "salesOrderNo": 12345,
        "invoiceNo": 67890
      }
    }
  }
}
```

**Resulting properties (merged):**

```json theme={null}
{
  "properties": {
    "unleashed": {
      "orderId": "ABC-123",
      "syncedAt": "2025-01-15T10:00:00Z"
    },
    "sage200Evo": {
      "salesOrderNo": 12345,
      "invoiceNo": 67890
    },
    "customField": "value"
  }
}
```

**Nested merge example:**

If you then PATCH:

```json theme={null}
{
  "order": {
    "properties": {
      "sage200Evo": {
        "shipmentId": "SHIP-999"
      }
    }
  }
}
```

The `sage200Evo` object is merged (not replaced):

```json theme={null}
{
  "properties": {
    "unleashed": { /* unchanged */ },
    "sage200Evo": {
      "salesOrderNo": 12345,      // ✅ Preserved
      "invoiceNo": 67890,          // ✅ Preserved
      "shipmentId": "SHIP-999"     // ✅ Added
    },
    "customField": "value"
  }
}
```

### Querying by Properties

Use property filters to find orders based on custom data:

**Find orders synced to Sage 200:**

```bash theme={null}
?propertyPath=sage200Evo.salesOrderNo&propertyExists=true
```

**Find orders NOT yet synced to Sage 200:**

```bash theme={null}
?propertyPath=sage200Evo.salesOrderNo&propertyExists=false
```

**Find order with specific external ID:**

```bash theme={null}
?propertyPath=externalSystem.orderId&propertyValue=12345
```

<Warning>
  **Property filter limitations:**

  * `propertyValue` and `propertyExists` are mutually exclusive
  * Both require `propertyPath` to be specified
  * Property paths use dot notation (e.g., `system.subfield.value`)
</Warning>

### Common Integration Pattern

A typical ERP/external system integration workflow:

1. **Fetch unsynced orders:**
   ```bash theme={null}
   GET /api/v1/orders?phase=PENDING&status=UNASSIGNED&propertyPath=sage200Evo.salesOrderNo&propertyExists=false
   ```

2. **Process in your system** (create sales order, get order number)

3. **Update order with external reference:**
   ```bash theme={null}
   PATCH /api/v1/orders/{id}
   {
     "order": {
       "properties": {
         "sage200Evo": {
           "salesOrderNo": 12345,
           "syncedAt": "2025-02-20T14:30:00Z"
         }
       }
     }
   }
   ```

4. **Later, add more external data** (merges automatically):
   ```bash theme={null}
   PATCH /api/v1/orders/{id}
   {
     "order": {
       "properties": {
         "sage200Evo": {
           "invoiceNo": 67890,
           "invoicedAt": "2025-02-25T09:00:00Z"
         }
       }
     }
   }
   ```

## Pagination & Rate Limits

* Pagination is standard `page` + `limit` (max 100). Default: page 1, limit 20.
* Rate limits return HTTP 429 with a `Retry-After` header (seconds). Back off and retry after that interval.

## Errors

* 400: Validation errors (e.g., invalid timestamps, mutually-exclusive filters, missing propertyPath)
* 401: Missing or invalid authentication headers
* 404: Order not found or doesn't belong to your business
* 429: Too many requests — honor `Retry-After`
* 500: Unexpected server error

## Best Practices

* **Incremental syncs**: Use `modifiedSince` to fetch only changed orders
* **Property filters**: Use `propertyExists=false` to find orders needing processing
* **Idempotency**: Always use idempotency keys for POST operations
* **Property structure**: Namespace your properties by system (e.g., `sage200Evo`, `unleashed`, `myErp`)
* **Timestamps**: Store sync timestamps in properties for debugging and monitoring
* **Error handling**: Orders you don't own return 404 (not 403) to prevent information leakage
