Skip to main content

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.
All endpoints require header-based auth: X-Business-ID and X-API-Key.

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)
    • Mutual exclusivity: 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)
    • Mutual exclusivity: 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 high-level lifecycle 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 physical movement/fulfillment state:
  • 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
Phase and status work together. For example, an order can have phase: CANCELLED but status: SHIPPED if it was cancelled after already being dispatched.

Example Queries

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"
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

Response Structure

Each order includes buyer context and full item details:
{
  "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:
curl "https://integrations.polinate.ai/api/v1/orders/ord_abc123" \
  -H "X-Business-ID: {{businessId}}" \
  -H "X-API-Key: {{apiKey}}"
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.
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"
    }
  }'
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")
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}}"
  }'

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:
{
  "properties": {
    "unleashed": {
      "orderId": "ABC-123",
      "syncedAt": "2025-01-15T10:00:00Z"
    },
    "customField": "value"
  }
}
PATCH request:
{
  "order": {
    "properties": {
      "sage200Evo": {
        "salesOrderNo": 12345,
        "invoiceNo": 67890
      }
    }
  }
}
Resulting properties (merged):
{
  "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:
{
  "order": {
    "properties": {
      "sage200Evo": {
        "shipmentId": "SHIP-999"
      }
    }
  }
}
The sage200Evo object is merged (not replaced):
{
  "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:
?propertyPath=sage200Evo.salesOrderNo&propertyExists=true
Find orders NOT yet synced to Sage 200:
?propertyPath=sage200Evo.salesOrderNo&propertyExists=false
Find order with specific external ID:
?propertyPath=externalSystem.orderId&propertyValue=12345
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)

Common Integration Pattern

A typical ERP/external system integration workflow:
  1. Fetch unsynced orders:
    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:
    PATCH /api/v1/orders/{id}
    {
      "order": {
        "properties": {
          "sage200Evo": {
            "salesOrderNo": 12345,
            "syncedAt": "2025-02-20T14:30:00Z"
          }
        }
      }
    }
    
  4. Later, add more external data (merges automatically):
    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