Optimistic Concurrency Control (OCC)
Overview
Sivo APIs implement Optimistic Concurrency Control (OCC) for PATCH requests. When data is retrieved, used by a client application, and subsequently updated there is a risk of a race condition where another update happens in between. This can result in data loss and unexpected behavior.
The If-Match HTTP header is used for optimistic concurrency control in PATCH APIs. It ensures that updates to resources are only applied if the resource hasn't been modified since the client last retrieved it.
Header Format
if-match: <datetime-string>
Usage
The if-match header is required for all PATCH requests that modify existing resources. The value should be the updated_at timestamp of the resource being updated.
Example Request
// Example using fetch
const response = await fetch('https://core.staging.sivo.com/buyers/123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'if-match': '2025-07-09T18:30:00.000Z' // The last known updated_at timestamp
},
body: JSON.stringify({
// Your update fields here
})
});
if (!response.ok) {
if (response.status === 412) {
console.error('Resource was modified. Please refresh and try again.');
}
// Handle other errors
}Error Responses
400 Bad Request
Cause: Missing or invalid if-match header
Solution: Include a valid ISO 8601 datetime string in the if-match header
412 Precondition Failed
Cause: The resource has been modified since the provided timestamp
Solution:
- Fetch the latest version of the resource,
- Re-apply your changes to the latest version
- Resubmit with the new updated_at timestamp
Best Practices
For workflows that retrieve and update a resource:
- Retrieve the object and retain the value of the
updated_atfield. - Include the timestamp in the if-match header for any
PATCHrequests. - Handle
412errors by refreshing the resource and re-applying changes (if needed) - Use the latest
updated_attimestamp in the nextPATCHattempt.
Client-Side Implementation
class ApiClient {
private lastUpdated: string | null = null;
async updateBuyer(id: string, data: Partial<Buyer>): Promise<Buyer> {
if (!this.lastUpdated) {
throw new Error('Must fetch buyer before updating');
}
const response = await fetch(`/buyers/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'if-match': this.lastUpdated
},
body: JSON.stringify(data)
});
if (response.status === 412) {
// Handle concurrency conflict
throw new Error('Resource was modified. Please refresh and try again.');
}
if (!response.ok) {
throw new Error('Failed to update buyer');
}
const updatedBuyer = await response.json();
this.lastUpdated = updatedBuyer.updated_at;
return updatedBuyer;
}
}
