Why a success reply isn’t enough
A write API answers with a status code. A 200 or a 201 says the server accepted the request. It doesn’t say the stored record holds what the client sent. In FHIR, for example, a create sent with If-None-Exist that matches one existing resource is ignored: the server answers 200 OK and creates nothing.1 An update sent with an old version in If-Match gets 412 Precondition Failed, and nothing is written.1
Other gaps come from the server’s own rules. Access rules can make a field read-only, so the server saves the record without it. A server can replace an id the client chose, or set the author to the app that sent the request. On Medplum 5.2.6 without its transaction-bundles feature, an all-or-nothing batch with one invalid entry was answered 200, and two of its three readings were stored.4
When we measured this, a check that trusts the status code caught none of the 11 cases where the server said success and stored something else.3
How a read-back works
- Write the record and keep the server’s answer, including the id and version it reports.
- Read that record again, at that version if the API allows it.
- Drop the fields the server sets on its own, such as timestamps and version numbers.
- Compare each field the client sent with the stored value, and flag any difference.
The comparison is the useful part. “The record exists” is a weaker check than “the record holds what we meant to save”.
What a read-back can’t see
A read-back reads one record. It misses what happens around it:
- Extra records. A retry that saves the same reading twice leaves two records. Reading back one of them looks fine.
- Partial batches. If one entry of a batch was dropped, a read-back of the entries that were stored passes.
- Records the server made instead. A conditional update with no match can create a new record rather than change the old one.
- Later changes. Another system can change the record a second after the read-back.
For those, you need the server’s own change log, or a rule that stops the risky call before it runs. On the 6 controls of our benchmark, where the agent was right, Deedglass raised 0 false alarms.3
Read-back, log check, or both
A read-back works with any API you can read from, which is why it suits FHIR servers and REST tools. A check against the database’s change log sees every row a transaction committed, including triggers and cascades, but needs access to that log. Where both are possible, the log check sees more. Where only the API is open to you, a read-back is far better than trusting the status code.