# How Universal Commerce Protocol Manages Profile Caching and Discovery Footprint

> Learn how Universal Commerce Protocol manages profile caching and discovery footprint with a 60-second minimum cache TTL, HTTPS-only fetching, and LRU caches for efficient performance.

- Repository: [Universal Commerce Protocol (UCP)/ucp](https://github.com/Universal-Commerce-Protocol/ucp)
- Tags: how-to-guide
- Published: 2026-04-26

---

**Universal Commerce Protocol enforces a 60-second minimum cache TTL for profile responses, mandates HTTPS-only fetching with no redirects, and recommends fixed-size LRU caches combined with global rate limiting to bound discovery footprint.**

Universal Commerce Protocol (UCP) treats *profiles*—JSON documents describing business capabilities, signing keys, and service endpoints—as stable, cache-able resources. According to the specification in [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md), the protocol defines strict **caching requirements** and **discovery footprint boundaries** to ensure that profile fetching remains efficient even as the network scales to millions of parties. These mechanisms prevent discovery storms while maintaining the cryptographic trust established through validated profile documents.

## Mandatory Cache-Control Headers

Every profile response **must** include a `Cache-Control` header that is public and specifies a minimum `max-age` of **60 seconds**. This guarantees that compliant implementations can safely store profiles in shared caches for at least one minute, regardless of the origin’s specific directives.

> "Profile responses **MUST** include a `Cache-Control` header with `public` and `max-age` of at least 60 seconds." – [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md#L1035-L1037)

Because profiles represent a party's stable identity and capabilities rather than per-transaction configuration, they are inherently safe to cache. The specification reinforces this by requiring shared cache support with this minimum TTL floor, ensuring that high-traffic scenarios do not overwhelm origin servers with redundant requests.

## Discovery Footprint Management Strategies

To prevent unbounded growth in memory and network usage during discovery, UCP recommends a multi-layered approach to footprint reduction.

### Fixed-Size LRU Caching

Implementations should maintain a **fixed-size cache**, typically an **LRU (Least-Recently-Used)** cache, to store resolved profiles. This caps memory usage regardless of how many unique profile URLs are encountered across the network.

> "- **Fixed-size profile cache** (e.g., LRU) — bounds memory regardless of the number of unique profile URLs encountered." – [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md#L1065-L1067)

### Rate Limiting and Failure Back-off

Complementary to caching, the protocol recommends **global rate limits** on outbound discovery fetches to prevent traffic spikes. Additionally, implementations should implement **exponential back-off** on repeated failures to reduce load against hostile or temporarily unavailable endpoints.

> "- **Global rate limit** on discovery fetches … - **Backoff on repeated failures** …" – [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md#L1067-L1070)

### Asynchronous Discovery Mechanisms

When a profile cannot be resolved synchronously without blocking the request, UCP permits returning a `503 Service Unavailable` response with a `Retry-After` header. The implementation fetches the profile in the background, and subsequent client retries hit the now-cached profile.

> "- **Asynchronous discovery** — defer profile resolution by responding with a `503` … and resolve the profile in the background; when the platform retries, the validated profile is cached and capability negotiation proceeds synchronously." – [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md#L1071-L1074)

## Safety Requirements for Profile Fetching

UCP prescribes strict transport rules to prevent abuse during profile retrieval. As defined in [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md#L1078-L1080), implementations:

1. **Must** reject profile URLs not served over HTTPS.
2. **Must not** follow HTTP redirects (3xx responses).
3. **Should** enforce reasonable connect and response timeouts to prevent hanging connections.

These constraints ensure that profile discovery does not introduce open redirects or man-in-the-middle vulnerabilities, maintaining the integrity of the trust chain established in [`source/discovery/profile_schema.json`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/source/discovery/profile_schema.json).

## Implementation Examples

The following code demonstrates practical application of the caching, rate-limiting, and safety requirements specified in the UCP documentation.

### Python LRU Profile Cache with Safety Checks

This example implements a fixed-size LRU cache that enforces the HTTPS-only rule, rejects redirects, and validates the mandatory `Cache-Control` header.

```python
from collections import OrderedDict
import requests
import time

class LRUProfileCache:
    def __init__(self, max_items: int = 256, ttl: int = 60):
        self.max_items = max_items
        self.ttl = ttl                 # seconds

        self.store = OrderedDict()     # url -> (timestamp, profile)

    def _is_fresh(self, ts: float) -> bool:
        return (time.time() - ts) < self.ttl

    def get(self, url: str):
        # Return cached profile if fresh

        entry = self.store.get(url)
        if entry and self._is_fresh(entry[0]):
            # refresh LRU order

            self.store.move_to_end(url)
            return entry[1]
        # otherwise fetch

        return self._fetch_and_store(url)

    def _fetch_and_store(self, url: str):
        # Enforce HTTPS and no redirects

        if not url.startswith("https://"):
            raise ValueError("Profile URL must use HTTPS")
        resp = requests.get(url, allow_redirects=False, timeout=5)
        resp.raise_for_status()
        # Verify required Cache‑Control header

        cc = resp.headers.get("Cache-Control", "")
        if "public" not in cc or "max-age" not in cc:
            raise ValueError("Profile response missing required Cache-Control")
        profile = resp.json()
        # Insert into cache

        self.store[url] = (time.time(), profile)
        self.store.move_to_end(url)
        if len(self.store) > self.max_items:
            self.store.popitem(last=False)   # evict LRU

        return profile

```

### Node.js Global Rate Limiter

This example uses `bottleneck` to enforce global rate limits on discovery traffic, ensuring the **global rate limit** strategy caps outbound requests.

```javascript
const Bottleneck = require('bottleneck');

// One global limiter for all outbound profile fetches
const limiter = new Bottleneck({
  maxConcurrent: 5,
  minTime: 200          // at most 5 requests per second
});

async function fetchProfile(url) {
  return limiter.schedule(() => _fetch(url));
}

async function _fetch(url) {
  if (!url.startsWith('https://')) throw new Error('HTTPS required');
  const res = await fetch(url, { redirect: 'error', timeout: 5000 });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const cc = res.headers.get('cache-control') || '';
  if (!/public/.test(cc) || !/max-age=/.test(cc)) {
    throw new Error('Invalid Cache-Control header');
  }
  return await res.json();
}

```

### Asynchronous Discovery with 503 Response

This pattern demonstrates handling missing profiles by returning a `503` status, allowing background fetching while keeping the client request non-blocking.

```python
def handle_request(request):
    profile_url = request.headers.get('UCP-Agent')
    try:
        profile = profile_cache.get(profile_url)   # may raise

    except Exception:
        # Profile not yet cached – reply with 503 and let client retry

        return (503, {'Retry-After': '5'}, b'Profile discovery in progress')
    # Continue with normal processing using `profile`

    …

```

## Summary

- **Cache-Control is mandatory**: All profile responses must include `Cache-Control: public, max-age=60` or higher, as defined in [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md).
- **Memory is bounded**: Fixed-size LRU caches prevent discovery footprint from growing with network size.
- **Traffic is shaped**: Global rate limits and failure back-off protect against spikes and retry storms.
- **Security is enforced**: HTTPS-only fetching with no redirects prevents interception and open-redirect attacks.
- **Async resolution is supported**: `503` responses with `Retry-After` allow non-blocking profile discovery.

## Frequently Asked Questions

### What is the minimum cache duration for UCP profiles?

According to [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md) lines 1035-1037, profile responses **must** include a `Cache-Control` header specifying `public` and a `max-age` of at least **60 seconds**. This guarantees that shared caches can store profiles for a minimum of one minute.

### How does Universal Commerce Protocol prevent memory exhaustion during discovery?

The specification recommends implementing a **fixed-size LRU cache** for storing profiles. As noted in [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md) lines 1065-1067, this strategy bounds memory usage regardless of how many unique profile URLs a platform encounters, preventing unbounded growth as the network scales.

### What happens when a profile cannot be fetched synchronously?

Per [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md) lines 1071-1074, implementations **may** return a `503 Service Unavailable` status with a `Retry-After` header and resolve the profile asynchronously in the background. When the client retries, the validated profile is already cached, allowing capability negotiation to proceed synchronously.

### Why does UCP disallow redirects for profile fetching?

To prevent open-redirect vulnerabilities and man-in-the-middle attacks, the protocol mandates that implementations **must not** follow HTTP redirects (3xx responses) and **must** reject non-HTTPS URLs. These requirements are specified in [`docs/specification/overview.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/overview.md) lines 1078-1080 as part of the core safety rules for profile retrieval.