ETag support for the REST API #419

Closed
opened 2026-04-05 16:31:20 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @jeremystretch on 2/4/2026

NetBox version

v4.5.2

Feature type

New functionality

Proposed functionality

Add support for ETags to the REST API. This entails two functions:

  • Include an ETag header on the response to a REST API request for an individual object. (It's not clear whether ETags could be supported on list views.)
  • Recognize and respect the If-Match header if included on a PUT/PATCH request. If the ETag specified by the If-Match header is outdated, return a 412 (precondition failed) response.

The mechanism for generating an ETag is not specified by the standard. It probably makes sense to use the last_updated (or, if not set, created) timestamp for the object being retrieved. This obviates the need to generate a hash for the object.

It's worth noting that we're pursuing ETag support specifically as opposed to e.g. support for the If-Modified-Since header as it and the Last-Modified header are limited to second precision: This is insufficient to guard against competing writes from multiple clients. Our native timestamps support millisecond precision.

(This functionality was originally proposed in #4917 but did not capture a sufficient use case.)

Use case

The primary benefit of this functionality is to protect against conflicting updates to an object from multiple clients. A simple scenario (using serialized ETags):

  1. Client A requests site 1 (ETag = 100).
  2. Client B requests site 1 (ETag = 100).
  3. Client A updates the status of site 1 with If-Match: 100. The update succeeds, and the site's ETag is now 200.
  4. Client B updates the status of site 1 with If-Match: 100. The update fails, and NetBox returns a 412 response informing the client that its cached copy of the object is no longer valid.
  5. Client B requests site 1 again (ETag = 200) and determines whether an update is still needed.

A secondary benefit is potentially recognized using the If-None-Match header, which directs the API to return the object only if its ETag has been modified from the one given. However, since the server must first fetch the object from the database to retrieve its ETag (timestamp) for comparison anyway, this may be of little practical value without some external caching in place.

Database changes

None expected, unless we decide to store ETag values in the database for some reason.

External dependencies

N/A

*Originally created by @jeremystretch on 2/4/2026* ### NetBox version v4.5.2 ### Feature type New functionality ### Proposed functionality Add support for ETags to the REST API. This entails two functions: * Include an [`ETag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag) header on the response to a REST API request for an individual object. (It's not clear whether ETags could be supported on list views.) * Recognize and respect the [`If-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Match) header if included on a PUT/PATCH request. If the ETag specified by the `If-Match` header is outdated, return a [412 (precondition failed)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/412) response. The mechanism for generating an ETag is not specified by the standard. It probably makes sense to use the `last_updated` (or, if not set, `created`) timestamp for the object being retrieved. This obviates the need to generate a hash for the object. It's worth noting that we're pursuing ETag support specifically as opposed to e.g. support for the [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Modified-Since) header as it and the `Last-Modified` header are limited to second precision: This is insufficient to guard against competing writes from multiple clients. Our native timestamps support millisecond precision. (This functionality was originally proposed in #4917 but did not capture a sufficient use case.) ### Use case The primary benefit of this functionality is to protect against conflicting updates to an object from multiple clients. A simple scenario (using serialized ETags): 1. Client A requests site 1 (ETag = 100). 2. Client B requests site 1 (ETag = 100). 3. Client A updates the status of site 1 with `If-Match: 100`. The update succeeds, and the site's ETag is now 200. 4. Client B updates the status of site 1 with `If-Match: 100`. The update fails, and NetBox returns a 412 response informing the client that its cached copy of the object is no longer valid. 5. Client B requests site 1 again (ETag = 200) and determines whether an update is still needed. A secondary benefit is potentially recognized using the [`If-None-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match) header, which directs the API to return the object only if its ETag has been modified from the one given. However, since the server must first fetch the object from the database to retrieve its ETag (timestamp) for comparison anyway, this may be of little practical value without some external caching in place. ### Database changes None expected, unless we decide to store ETag values in the database for some reason. ### External dependencies N/A
MrUnknownDE added the status: acceptedstatus: acceptedtype: featurecomplexity: mediumstatus: acceptednetboxstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptednetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumtype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: feature labels 2026-04-05 16:31:29 +02:00
Sign in to join this conversation.
No Label complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/netbox#419