# 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: | Scenario | Behavior | Example | | --- | --- | --- | | **Field omitted** | Not modified (current value preserved) | Request doesn't include `externalNotes` → field stays unchanged | | **Field provided with value** | Updated to the new value | `"status": "booked"` → field updates to `"booked"` | | **Field set to null** | Cleared (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 ```http 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 ```http 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: ```json { "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`: ```json { "externalNotes": null } ``` Result: `externalNotes` is now `null` **Omitting a field** does NOT clear it - it remains unchanged: ```json { "status": "booked" // externalNotes is NOT included, so it stays as-is } ``` ### Nested Objects For nested objects, you can update individual fields within the object: ```http 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**: ```http 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 ```bash curl -X PATCH https://api.mvmnt.io/v1/orders/ORD-12345 \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"status": "booked"}' ``` ### Pattern 2: Update Multiple Related Fields **Use Case:** Update shipment with tracking and ETA ```bash 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 ```bash 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 ```bash 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 ```bash 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 ```javascript 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 ```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 ```http PATCH /v1/orders/ORD-12345 { "status": "invalid_status" } ``` **Response:** `400 Bad Request` ```json { "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:** ```http PATCH /v1/orders/ORD-12345 { "status": "booked" } ``` **Response:** `200 OK` ```json { "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: ```http 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` ```json { "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:** ```json { "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 - [API Reference](/apis/openapi) - See which fields are updatable for each resource - [Error Handling](/getting-started/errors) - Learn how to handle validation errors - [Using Client Keys](/getting-started/client-keys) - Track your system's IDs in MVMNT