Skip to content
Last updated

Partial Updates

The MVMNT API strongly encourages using PATCH requests for updating resources. Partial updates allow you to modify only specific fields without sending the entire resource payload.

Why Partial Updates?

✅ Benefits

  1. Reduced Payload Size - Only send the fields you want to change
  2. Better Performance - Less data transfer and faster processing
  3. Avoid Overwrites - Don't accidentally clear fields you didn't mean to change
  4. Concurrent Safety - Multiple clients can update different fields without conflicts
  5. Simpler Code - No need to fetch full resource before updating

❌ Full Updates Are Discouraged

While the API technically supports PUT for full resource replacement, we strongly discourage this approach because:

  • Risk of data loss if you omit fields
  • Larger payloads
  • Higher chance of concurrent update conflicts
  • Requires fetching the full resource first

Always use PATCH for updates unless you have a specific reason not to.

How Partial Updates Work

With PATCH, you only include the fields you want to update. All other fields remain unchanged.

PATCH Request Behavior

Understanding how PATCH handles different field scenarios:

ScenarioBehaviorExample
Field omittedNot modified (current value preserved)Request doesn't include externalNotes → field stays unchanged
Field provided with valueUpdated to the new value"status": "booked" → field updates to "booked"
Field set to nullCleared (set to null) where nullable"externalNotes": null → field cleared

Key Principle: Only send what you want to change. Everything else stays as-is.

Example: Update Order Status

PATCH /v1/orders/ORD-12345
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "status": "booked"
}

Only the status field is updated. All other fields (stops, freight, charges, notes, etc.) remain exactly as they were.

Example: Update Multiple Fields

PATCH /v1/orders/ORD-12345
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "status": "in_transit",
  "externalNotes": "Driver called - ETA 2pm",
  "key": "ERP-ORD-789"
}

Only status, externalNotes, and key are updated.

Field-Level Behavior

Scalar Fields (strings, numbers, booleans)

Providing a value replaces the existing value:

{
  "externalNotes": "New notes"
}

Result: externalNotes is now "New notes" (previous value is replaced)

Setting Fields to Null

To clear a nullable field, explicitly set it to null:

{
  "externalNotes": null
}

Result: externalNotes is now null

Omitting a field does NOT clear it - it remains unchanged:

{
  "status": "booked"
  // externalNotes is NOT included, so it stays as-is
}

Nested Objects

For nested objects, you can update individual fields within the object:

PATCH /v1/orders/ORD-12345

{
  "freight": {
    "weight": 16000,
    "commodityDescription": "Updated description"
  }
}

Behavior: Only the specified fields within freight are updated. Other freight fields (handlingUnits, handlingUnitType, etc.) remain unchanged.

Arrays

For array fields, the entire array is replaced:

PATCH /v1/orders/ORD-12345

{
  "charges": [
    {
      "chargeCodeId": "550e8400-e29b-41d4-a716-446655440000",
      "amount": 1500.00,
      "description": "Updated linehaul"
    }
  ]
}

Behavior: The charges array is completely replaced with the new array. Previous charges are removed.

To preserve existing items, you must include them in the update:

  1. Fetch the current resource (GET)
  2. Modify the array in your application
  3. Send the complete modified array in PATCH

Common Patterns

Pattern 1: Update Single Field

Use Case: Change order status after booking

curl -X PATCH https://api.mvmnt.io/v1/orders/ORD-12345 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "booked"}'

Use Case: Update shipment with tracking and ETA

curl -X PATCH https://api.mvmnt.io/v1/shipments/SHP-67890 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "trackingNumber": "TRK-ABC123",
    "estimatedDelivery": "2025-01-22T14:00:00Z"
  }'

Pattern 3: Clear Optional Field

Use Case: Remove external notes

curl -X PATCH https://api.mvmnt.io/v1/orders/ORD-12345 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"externalNotes": null}'

Pattern 4: Update Nested Object

Use Case: Update freight weight without changing other freight details

curl -X PATCH https://api.mvmnt.io/v1/orders/ORD-12345 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "freight": {
      "weight": 18000
    }
  }'

Pattern 5: Add Client Reference

Use Case: Associate MVMNT order with your system's ID

curl -X PATCH https://api.mvmnt.io/v1/orders/ORD-12345 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "MY-SYSTEM-ID-789"}'

Code Examples

JavaScript / Node.js

async function updateOrder(orderId, updates) {
  const response = await fetch(`https://api.mvmnt.io/v1/orders/${orderId}`, {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(updates),
  });

  if (!response.ok) {
    throw new Error(`Update failed: ${response.statusText}`);
  }

  return response.json();
}

// Usage - update only status
await updateOrder('ORD-12345', {
  status: 'booked'
});

// Usage - update multiple fields
await updateOrder('ORD-12345', {
  status: 'in_transit',
  externalNotes: 'Driver confirmed pickup',
  key: 'ERP-12345'
});

Python

import requests

def update_order(order_id: str, updates: dict) -> dict:
    response = requests.patch(
        f'https://api.mvmnt.io/v1/orders/{order_id}',
        headers={
            'Authorization': f'Bearer {access_token}',
            'Content-Type': 'application/json',
        },
        json=updates
    )
    response.raise_for_status()
    return response.json()

# Usage - update only status
update_order('ORD-12345', {
    'status': 'booked'
})

# Usage - update multiple fields
update_order('ORD-12345', {
    'status': 'in_transit',
    'externalNotes': 'Driver confirmed pickup',
    'key': 'ERP-12345'
})

Validation

PATCH requests are validated the same as POST/PUT requests:

  • Required fields (for creation) are NOT required in PATCH
  • Field types must match schema (string, number, boolean, etc.)
  • Enum values must be valid (e.g., status must be one of the allowed values)
  • References must exist (e.g., locationId must reference a valid location)

Validation Error Example

PATCH /v1/orders/ORD-12345

{
  "status": "invalid_status"
}

Response: 400 Bad Request

{
  "error": "validation_error",
  "message": "Invalid field values",
  "details": [
    {
      "field": "status",
      "message": "Must be one of: draft, quoted, booked, dispatched, in_transit, delivered, canceled"
    }
  ]
}

Response

Successful PATCH requests return the full updated resource:

Request:

PATCH /v1/orders/ORD-12345

{
  "status": "booked"
}

Response: 200 OK

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "friendlyId": "ORD-12345",
  "status": "booked",
  "organizationId": "org_abc123",
  "key": "ERP-12345",
  "stops": [...],
  "freight": {...},
  "charges": [...],
  "totalRevenue": 1500.00,
  "createdAt": "2025-01-15T10:00:00Z",
  "updatedAt": "2025-01-15T14:30:00Z"
}

Note that updatedAt is automatically set to the current timestamp.

Concurrency

Partial updates help reduce concurrency conflicts, but conflicts can still occur:

Optimistic Locking (Future)

We plan to support optimistic locking using an etag or version field:

PATCH /v1/orders/ORD-12345
If-Match: "version-123"

{
  "status": "booked"
}

If another client updated the resource since you fetched it, you'll receive:

Response: 412 Precondition Failed

{
  "error": "precondition_failed",
  "message": "Resource was modified by another request",
  "currentVersion": "version-124"
}

Solution: Re-fetch the resource, reapply your changes, and retry.

Note: This feature is planned for a future release. Currently, last-write-wins.

Best Practices

✅ Do

  • Use PATCH for all updates (not PUT)
  • Only include fields you want to change
  • Include key when updating if you track entities in your system
  • Validate before sending to avoid unnecessary API calls
  • Handle validation errors gracefully

❌ Don't

  • Don't use PUT unless you truly need full replacement
  • Don't fetch-then-update unless necessary (e.g., for arrays)
  • Don't send unchanged fields (wastes bandwidth)
  • Don't assume PATCH semantics for arrays (arrays are replaced entirely)

Troubleshooting

Updates Don't Seem to Apply

Problem: Field values aren't changing

Solutions:

  1. Verify the field name matches the schema exactly (case-sensitive)
  2. Check that you're sending valid JSON
  3. Ensure Content-Type: application/json header is set
  4. Verify the field is writable (some fields are read-only)

Array Items Disappearing

Problem: Existing array items are removed

Explanation: This is expected behavior - arrays are replaced entirely in PATCH requests.

Solution: To preserve existing items, include them in your update payload.

"Field is read-only" Error

Problem: Cannot update certain fields

Example:

{
  "error": "validation_error",
  "details": [
    {
      "field": "id",
      "message": "This field is read-only"
    }
  ]
}

Solution: Remove read-only fields from your update payload. Common read-only fields:

  • id
  • friendlyId
  • createdAt
  • organizationId

Next Steps