Skip to content
Last updated

Partial Updates

Use PATCH to update resources in the MVMNT API. A PATCH request changes only the fields you send—everything you leave out stays unchanged.

Why Partial Updates?

Benefits

  1. Smaller requests — send only what changed
  2. Faster updates — less data to transfer and process
  3. Fewer accidental overwrites — omitted fields aren’t touched
  4. Safer concurrent edits — different clients can update different fields with fewer collisions
  5. Less glue code — you usually don’t need a read-before-write cycle

Full Updates (PUT) Are Discouraged

The API may support PUT for full replacement, but treat it as a last resort:

  • Easy to lose data by omitting fields
  • Larger payloads
  • More likely to collide with concurrent updates
  • Often forces you to fetch the full resource first

Default to PATCH for updates unless you truly need full replacement semantics.

How Partial Updates Work

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

PATCH Request Behavior

How PATCH behaves depending on what you send:

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

Rule: Send only what you intend to change.

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 reduce concurrency problems, but they don’t eliminate them. If two clients PATCH the same field, the last write wins.

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 updates (not PUT)
  • Send only the fields you intend to change
  • Set fields to null only when you want to clear them
  • Treat arrays as replace-only (GET → edit → PATCH the full array)
  • Handle validation errors and retry with corrected input

Don't

  • Don't use PUT unless you truly want full replacement
  • Don't send fields you’re not changing (more surface area for mistakes)
  • Don't assume array merge semantics (PATCH replaces the whole array)

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