Skip to content

HATEOAS Conditional Requests — Using ETags and If-None-Match in Hypermedia APIs

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about HATEOAS Conditional Requests. We cover key concepts, practical examples, and best practices to help you master this topic.

HATEOAS conditional requests use ETags and If-None-Match headers to cache hypermedia responses, reducing bandwidth by only returning full responses when links or resource state changes.

What You'll Learn

  • ETag generation strategies for hypermedia resources
  • Conditional GET with If-None-Match for Caching
  • Optimistic concurrency with If-Match and If-Unmodified-Since
  • Cache invalidation when links change
  • Combining HATEOAS with HTTP caching headers

Why It Matters

Hypermedia responses often contain many links that are expensive to generate and transmit. Conditional requests reduce bandwidth by 60-80% for read-heavy APIs. DodaTech's hypermedia threat analysis API uses conditional requests to serve thousands of concurrent security scanners without overwhelming the database.

Real-World Use

A threat intelligence API returns hypermedia responses with links to related threat reports, analysis actions, and remediation guides. With conditional requests, scanners re-fetch only when new threats appear, reducing bandwidth by 75%.

sequenceDiagram
    Client->>Server: GET /threats/123 (If-None-Match: "abc123")
    Server->>Server: Check ETag match
    alt ETag matches
        Server-->>Client: 304 Not Modified (empty body)
    else ETag changed
        Server-->>Client: 200 OK + ETag: "def456" + Hypermedia body
    end
    Client->>Client: Updates cached representation

Code Examples

import hashlib
import json

def generate_etag(resource_id, links, updated_at):
    """Generate ETag based on resource data and links."""
    data = {
        'id': resource_id,
        'links': sorted(links, key=lambda l: l['rel']),
        'updated_at': updated_at.isoformat()
    }
    content = json.dumps(data, sort_keys=True).encode()
    return hashlib.sha256(content).hexdigest()

links = [
    {'rel': 'self', 'href': '/threats/123'},
    {'rel': 'analysis', 'href': '/threats/123/analysis'},
    {'rel': 'remediation', 'href': '/threats/123/fix'}
]
etag = generate_etag('123', links, datetime.now())
print(f"ETag: {etag}")
# Output: ETag: a1b2c3d4e5f6...

Example 2: Conditional GET Handler

from flask import Flask, request, jsonify, make_response

app = Flask(__name__)

@app.route('/threats/<threat_id>')
def get_threat(threat_id):
    threat = find_threat(threat_id)
    hypermedia = build_hypermedia(threat)
    body = json.dumps(hypermedia)
    current_etag = hashlib.md5(body.encode()).hexdigest()

    # Check If-None-Match
    client_etag = request.headers.get('If-None-Match', '').strip('"')
    if client_etag == current_etag:
        return make_response('', 304)

    response = make_response(hypermedia, 200)
    response.headers['ETag'] = f'"{current_etag}"'
    response.headers['Cache-Control'] = 'no-cache'
    return response
@app.route('/threats/<threat_id>/analyze', methods=['POST'])
def analyze_threat(threat_id):
    client_etag = request.headers.get('If-Match', '').strip('"')

    threat = find_threat(threat_id)
    current_etag = compute_etag(threat)

    if client_etag != current_etag:
        return jsonify({
            'error': 'Resource changed since last fetch',
            'status': 412,
            'links': [
                {'rel': 'self', 'href': f'/threats/{threat_id}'},
                {'rel': 'latest', 'href': f'/threats/{threat_id}?refresh=true'}
            ]
        }), 412

    # Proceed with analysis
    result = run_analysis(threat)
    new_links = [
        {'rel': 'self', 'href': f'/threats/{threat_id}'},
        {'rel': 'results', 'href': f'/threats/{threat_id}/results/{result.id}'}
    ]
    response = jsonify({'status': 'analyzing', '_links': new_links})
    response.headers['ETag'] = f'"{generate_etag(threat_id, new_links, datetime.now())}"'
    return response

Common Mistakes

1. Using Weak ETags for Hypermedia

Weak ETags allow semantically equivalent but byte-different responses. Hypermedia changes must use strong ETags.

2. Only Caching Response Bodies

Cache link structures separately from data — links change independently of resource state.

3. Ignoring Vary Header

Always set Vary: Accept, Authorization so different users get different hypermedia links.

4. Expiring Caches Too Aggressively

Use ETag validation instead of short max-age to avoid 304 round-trips on every request.

When links change (e.g., user permissions), the ETag must change even if resource data is identical.

Practice Questions

  1. What is the difference between strong and weak ETags for hypermedia?
  2. How does If-None-Match reduce bandwidth?
  3. Why is the Vary header important for hypermedia caching?
  4. When should you use If-Match instead of If-None-Match?
  5. How do you handle cache invalidation when user permissions change?

Answers:

  1. Strong ETags change when any byte changes; weak (~) allow semantic equivalence. Hypermedia uses strong ETags.
  2. It returns 304 Not Modified when the client's cached version is current, saving the full response body.
  3. Different clients may see different links based on Authorization or Accept headers.
  4. If-Match is for write operations (optimistic concurrency); If-None-Match is for read caching.
  5. Regenerate the ETag whenever user permissions change, even if resource data stays the same.

Challenge: Build a middleware that generates ETags for hypermedia responses by hashing both the resource data and link list. Verify that changing user roles produces a different ETag.

FAQ

What is a strong ETag?

: A strong ETag changes when the response body changes at the byte level. For hypermedia, it must change when links change.

Can I use Last-Modified instead of ETag?

: Yes, but ETags are more precise for hypermedia because links can change without modifying resource timestamps.

Should I cache hypermedia on CDN?

: Only with careful Vary header configuration. Different users may see different links.

How does conditional request affect hypermedia clients?

: Clients must store ETags and send If-None-Match on subsequent requests. Libraries like Traverson handle this automatically.

What happens when a link is removed?

: The ETag changes, so the client's cached response becomes stale and the next request returns a full response.

What's Next

Combine conditional requests with HATEOAS Caching for a complete caching Strategy, then explore HATEOAS Content Negotiation for serving different hypermedia formats.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro