Even a small edit can involve more than changing one stored value. Suppose a customer renames a reading list from a phone while the same list is open in a browser. The rename should preserve its identity and books, reject invalid values, and avoid letting an edit based on older data overwrite newer changes. A success response should make clear what the service actually saved.
An update API defines how an existing resource may change while keeping its identity.
This chapter uses a fictional bookstore API to explain editable fields, request and response contracts, validation, permissions, atomic changes, and uncertain outcomes. The examples assume an authenticated JSON API over HTTPS at https://api.bookstore.example.
Start with what the caller may change. The bookstore's reading-list update endpoint edits a list's name and visibility. It preserves the list's ID, owner, creation timestamp, and membership.
The target is /reading-lists/list_801. The path identifies the existing list; the body describes the requested change. Clients do not need to resubmit the ID or choose a new resource address.
This endpoint is update-only. If the list does not exist, the service returns 404 Not Found instead of creating one. That is an explicit API rule, not a property clients should infer from the word “update” or the HTTP method alone.
Also define when an update counts as successful. Here, an update succeeds only after the service commits all accepted metadata changes together. An immediate authorized direct read can observe that state or a newer state the service commits afterward. Success does not lock the resource against subsequent edits or guarantee that every discovery index has caught up.
The diagram separates the requested changes from the resulting resource:
The proposed state exists only for evaluation until the checks pass. The diagram describes responsibilities rather than requiring a particular order of storage operations; authorization must also control which current data the caller can access.
A response schema is not automatically an update schema. A client may be able to read a field without having permission to assign it.
This endpoint defines the following field rules:
The service rejects unknown and read-only input fields even when their values match current values. This makes accidental resubmission of an entire response visible to clients and keeps writable fields explicit. These rejection rules are API choices.
The name uses the same rule wherever a client assigns it: trim leading and trailing ASCII spaces, then require 1–100 Unicode code points. Renaming does not introduce a second definition of a valid name. Multiple lists can share a name, so a repeated display name is not a uniqueness conflict.
Some state changes carry more meaning than field assignment. Transferring ownership may require another person's acceptance. Cancelling an order may require stopping fulfillment and arranging a refund. A general metadata update should not let callers bypass those processes by assigning owner_id or status directly.
Use a dedicated operation or request resource when the business intention has distinct permissions, required effects, or its own lifecycle. The fact that storage represents a field as a string does not make every string value a permitted update.
For selected reading-list metadata changes, this API uses PATCH with application/merge-patch+json. The media type identifies JSON Merge Patch, the format that gives the request body its meaning. PATCH alone does not choose a patch format.
For this object, included editable fields propose new values and omitted fields stay unchanged. A merge-patch null requests removal of a field. Because both name and visibility must remain present, this API rejects their removal. It also rejects a patch whose resulting resource would cease to be an object.
Do not reapply creation defaults to omitted update fields. A rename of a public list must not silently reset its visibility to private. The service preserves the current value unless the request explicitly changes it.
Assume the owner has fetched list_801 with name Systems Reading, visibility private, and the response header ETag: "list-801-v7". An entity tag, which the service sends in ETag, is an opaque validator for a representation. This API supplies strong tags for this editable metadata representation and requires an If-Match header on updates.
The customer requests a rename based on that version:
The condition asks the service to apply the change only if the selected representation still matches the supplied tag. Clients copy tags as complete values, including their quotes; they do not calculate the next tag from the example's suffix.
The body contains only the intended edit. Sending an old visibility value alongside the name would also request assignment of that value, even if the user never touched the visibility control.
This example uses one patch format for a small object. Replacement updates and other partial-update formats need their own explicit contracts; choosing among them does not remove the need for field rules, permissions, or validation.
After committing the rename, the service returns:
The response contains the saved representation, not an echo of the patch. It confirms the effective name while preserving identity, ownership, visibility, and creation time. The new tag describes the returned representation.
Returning the representation lets clients display normalization and server-controlled values without reconstructing the resource locally. This API uses 200 OK consistently for completed metadata updates, including accepted changes that leave the values unchanged.
An API that returns no response content can use 204 No Content. Choose and document the success shape rather than switching unpredictably between a resource, an empty object, and an empty response. An ordinary update of this existing list does not need a new resource address.
A returned representation describes the state the operation produced. Another authorized writer can change it immediately afterward. That possibility does not make the original response false; it means the response is an observation of a completed update rather than a guarantee that the state will stay unchanged.
Each JSON body occupies one line, without trailing newlines. Tokens are placeholders. The private response cache policy is an example choice.
Validate both the submitted changes and the resource they would produce. Checking that a value has the right JSON type is not enough. A name can be a string and still become empty after normalization.
The owner submits two changes against the current version:
The name is invalid, so the entire update fails:
The list remains named Distributed Systems and remains private. Its tag is still "list-801-v8". The service does not save the valid visibility change separately.
PATCH requires the server to apply changes atomically: if it cannot apply the whole patch successfully, it must apply none of its changes. Atomic means other operations do not observe a partially applied patch. The diagram illustrates the rejected candidate:
A client can correct the name knowing that the rejected request did not publish the list. Saving permitted fields while ignoring invalid ones would make failure responses unreliable.
Rules involving multiple fields require the same treatment. For a delivery window, changing only starts_at must still leave it earlier than the saved ends_at. Validate the combined candidate rather than requiring clients to send every dependent field or checking each submitted value in isolation.
This API uses 400 Bad Request for malformed JSON, 415 Unsupported Media Type for an unsupported patch media type, and 422 for a well-formed patch that violates field or resulting-resource rules. Unsupported-format responses include Accept-Patch: application/merge-patch+json to identify the supported format. The application error codes and envelope are conventions, not HTTP-defined fields.
Authorize the operation and the proposed field changes. A support user may read a customer's list without permission to rename or publish it. A valid entity tag proves neither identity nor permission.
A support user with read-only access sends:
Because this user may know the list exists, the service returns an explicit denial:
The list remains unchanged. For another customer who may not know this private list exists, the endpoint uses the same generic 404 response as private-list reads. Missing or invalid credentials produce an authentication failure with an appropriate challenge.
Permissions can differ by field. A staff member may have permission to correct a catalog description but lack permission to change its price. Reject the entire request when it includes an unauthorized change rather than silently ignoring that field and reporting success.
Current business state can also restrict updates. An order's delivery address might be editable before packing begins but locked afterward. In such a contract, 409 Conflict can explain that the order's current state prevents the change. A syntactically valid address does not make the transition permissible.
Enforce these restrictions when applying the update, not solely when rendering an edit form. A resource, its relationships, or a caller's access can change between opening the form and saving it. Update handlers must coordinate relevant state checks with the write so competing actions cannot bypass the rule.
Two clients can start from the same representation and propose different valid updates. Without a condition, the later write may overwrite work the caller never saw. Updating fewer fields reduces accidental assignments but does not resolve competing edits to the same field.
This API checks If-Match as part of the guarded commit. Comparing the tag first and writing later without protection would leave a race between those steps.
The diagram shows two requests based on the same tag:
Either client could win; this illustration assumes A commits first. Now a browser still holding "list-801-v7" requests a different name:
The API's policy is to reject every failed version condition, including apparent repeats:
The service does not rename the list. The client can fetch the current representation and decide whether its intended edit still makes sense. Automatically replacing the old tag with a new one and resending stale form contents would defeat the protection.
An otherwise valid update without If-Match receives 428 Precondition Required under this API's policy. Clients editing a version they have read should send its strong tag. If-Match: * checks existence only; it does not detect edits since that read.
Keep failed version conditions distinct from business-state conflicts. The former means the supplied condition did not match; the latter can mean the API no longer allows the requested change even after the client refreshes its data.
Define what happens when an accepted update changes nothing. In this API, {} or setting the name to its current normalized value returns 200 with the current representation and tag, provided authorization and the version condition succeed. It does not change the tag or emit a business event saying the service renamed the list. Request audit logging may still record the attempt.
A timeout is different from a rejected update. The service may have committed the rename even though the client never received its response. Retrying with the original tag can then receive 412, because the first attempt changed the representation. That response does not prove the original attempt failed.
The client should retrieve the list and compare the result with its intent. A matching name shows that the list currently has the desired name, but it does not prove which request set it. If callers need reliable replay of one operation's outcome or protection for costly side effects, the API needs an explicit duplicate-request contract.
Do not assume all updates are naturally safe to repeat. Setting a field to a value differs from increasing a quantity, appending an item, or sending a notification. PATCH does not make every patch document idempotent. Decide retry behavior according to the requested effect and any concurrency conditions.
Separate the committed update from follow-up processing. A search-index refresh can run afterward if the API documents that delay. Record required follow-up work durably with the change so a process failure does not lose it, and avoid turning a completed rename into a failure merely because indexing falls behind.
Visibility changes need particular care: switching a list to private must update the access decision protected reads use. An eventual discovery update is not an excuse to keep serving private contents through an obsolete public access path.
If required business effects cannot complete within the request, model pending work explicitly and provide a way to inspect its outcome. Do not present acceptance for processing as a completed metadata update. A local transaction alone cannot make changes in independent remote systems atomic.
Define updates around permitted changes to an existing resource. Preserve identity, distinguish editable fields from business actions, and return a clear account of the saved result.
Validate the complete proposed state, enforce permissions and state restrictions, and apply each patch atomically. Make concurrent edits, unchanged values, retries, and delayed work explicit so clients can save changes without guessing what succeeded.