# Conditional reads

Source: https://docs.settlemint.com/docs/api-reference/guides/response-caching
Which Platform API reads carry an ETag, how to send it back in If-None-Match, what a 304 Not Modified means, and when a read carries no tag.

The platform caches successful responses to cacheable `GET` reads for each caller and tags them. A cacheable read answers with an `ETag`, a tag that identifies that exact response body. Send the tag back in `If-None-Match` on your next request for the same resource. If nothing changed, the platform answers `304 Not Modified` with no body. If something changed, it answers `200` with the new body and a new tag. A client that never sends `If-None-Match` receives the full response every time.

Polling with a tag replaces a full download with a status line. If you need to react to changes instead of polling for them, subscribe with [webhooks](https://docs.settlemint.com/docs/api-reference/webhooks).

## Send a conditional request [#send-a-conditional-request]

Make the read once and keep the `ETag` header from the response. This request is illustrative: replace the host and API key with your deployment values. See [Authentication](https://docs.settlemint.com/docs/api-reference/guides/authentication-api-keys) for the key.

```bash
curl -i "https://your-instance.example.com/api/v2/tokens" \
  -H "X-Api-Key: sm_dalp_..."
```

```http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-cache
ETag: "84e2cdb7a166d1671bc5e2abc1e78b59777dd1f863d3c5b05d09c2dd088444c2"
Vary: Accept-Encoding, X-Participant, X-Executor, X-Custody-Account-Id, X-Chain-Id, X-Network-Id, Origin
```

The example leaves out the route's ordinary body, which the [API reference](https://docs.settlemint.com/docs/api-reference/v2) describes. The route supplies `Content-Type`, cross-origin handling adds `Origin` to `Vary`, and the response cache sets the other headers. A real response carries more headers. Send the tag back on the next read:

```bash
curl -i "https://your-instance.example.com/api/v2/tokens" \
  -H "X-Api-Key: sm_dalp_..." \
  -H 'If-None-Match: "84e2cdb7a166d1671bc5e2abc1e78b59777dd1f863d3c5b05d09c2dd088444c2"'
```

```http
HTTP/1.1 304 Not Modified
Cache-Control: private, no-cache
ETag: "84e2cdb7a166d1671bc5e2abc1e78b59777dd1f863d3c5b05d09c2dd088444c2"
Vary: Accept-Encoding, X-Participant, X-Executor, X-Custody-Account-Id, X-Chain-Id, X-Network-Id, Origin
```

The `304` has no body. Keep the body you already hold and reuse it. Request the same URL, including the same query string, because a different URL is a different response.

The `If-None-Match` header follows these rules:

- It accepts one tag or a comma-separated list. The platform answers `304` when any tag in the list matches.
- `*` matches when the read has any current response that carries a tag.
- The platform compares tags strongly, so a weak tag, which starts with `W/`, never matches. A proxy that weakens tags therefore causes full responses.
- On a read that carries no tag, `If-None-Match` has no effect and the platform answers as if you had not sent it.

## Poll with conditional requests [#poll-with-conditional-requests]

For a client that watches a resource, send `If-None-Match` on every poll and replace your stored copy only on a `200`:

1. Make the first read and store the body with its `ETag`.
2. On each poll, send the stored tag in `If-None-Match`.
3. On `304`, keep the stored body.
4. On `200`, replace the stored body and tag with the new ones.
5. On any other status, handle it as an error as [Errors](https://docs.settlemint.com/docs/api-reference/guides/errors) describes and keep the stored pair.

On a `200` without an `ETag`, store the new body and drop the tag, so the next poll sends no `If-None-Match`.

## Use the SDK [#use-the-sdk]

The TypeScript SDK does this for you. It stores the tag from each successful read, sends it on the next read of the same URL for the same organisation and participant, and turns a `304` back into the stored body, so your code receives a normal response either way. It keeps at most 64 responses of at most 1 MiB each. It clears them when the organisation or the `X-Participant` header changes, and you must call `dalp.conditionalRequests.clear()` yourself when you switch identity through a separate authentication client. [Calling routes](https://docs.settlemint.com/docs/api-reference/sdk/calling-routes#conditional-reads) shows the call.

## What a `304` means [#what-a-304-means]

A `304` on a tag the response cache set says the body the platform would send now is byte for byte the body that your tag identifies. It is not a statement about the resource's history. A tag the response cache sets is a SHA-256 hash of the response body before compression, so a resource that changed and then changed back gives the same tag again, and the response encoding you negotiated with `Accept-Encoding` does not change the tag. When an operation sets its own `ETag`, such as the version of a stored record, the cache keeps that tag instead of replacing it with the body hash and matches `If-None-Match` against it, so a `304` on that read means the tag still matches. Treat every tag as opaque.

## Which reads carry a tag [#which-reads-carry-a-tag]

The response cache tags a read when all of these hold:

- The operation is a `GET` read and not an event stream. The cache does not apply to writes.
- The operation has not opted out of caching. Some reads, such as custody reconciliation and deposits, opt out and answer without a tag.
- The request does not set `live=true`. On a read whose reference lists a `live` query parameter, `live=true` skips the cache and returns the live result. Do not add it to other reads, because some reject unknown query parameters.
- The response has a `2xx` or `3xx` status, fits in 1 MiB, and is not streamed, so an error carries no cache tag.
- The operation does not itself set `Cache-Control: no-store` or `no-cache`, a cookie, or an `Authorization` header on its response.
- The platform can resolve which participant the request acts as, which `X-Participant` selects, or the read gives the same answer for every participant. A read that depends on the participant and cannot be resolved is served without a tag. A read that does not depend on the participant is tagged even then.
- The request URL and the caller's identity together stay within 16 KiB. A longer request is not cached.
- Response caching is on, which is the default. A self-hosting operator can turn it off with the `CACHE_VERSIONED_ENABLED` environment variable. That switch turns off the versioned cache that response caching builds on, and then the cache tags no read.

A read that skips the cache, for example after a cache fault or when catch-up after your own write cannot complete, answers without a cache tag. A response without an `ETag` is not an error. Store its body without a tag. A few operations set their own `ETag` and `Cache-Control`, so they answer with their own tag whether or not the cache stores the response. The cache does not store a `304` that an operation answers itself. The operation's reference says whether it answers `304`. The OpenAPI contract does not list the tag on every read that carries one, so check the response.

A browser app can read the tag and send `If-None-Match`: the API allows the request header and exposes the response header to cross-origin callers. A browser's HTTP cache also stores tagged responses and revalidates them by itself, so a browser app saves the transfer even if its code never sends the header. The code still receives the full response.

## Who shares a cached response [#who-shares-a-cached-response]

The platform keeps cached responses apart per caller. A cached response is keyed to the organisation, the user, how the caller signed in, the caller's platform role, whether the caller owns the organisation, the chain the request resolved to, the participant the request acts as, and the values of the `X-Executor`, `X-Custody-Account-Id`, `X-Chain-Id` and `X-Network-Id` request headers. A participant is the person, organisation or account record a request acts for, which `X-Participant` selects. For a read whose answer depends on the participant, the key holds the participant the platform resolved, not the raw header value. The platform learns which reads do not depend on the participant, such as the public configuration and the enabled languages. It keys those on the raw `X-Participant` header value instead and serves them without resolving the participant. A read that writes an audit record always resolves it. The key holds no asset role or on-chain role. The response `Vary` header lists `X-Participant` and the other four headers. Keep one stored tag per caller and per combination of those headers.

## How fresh a cached read is [#how-fresh-a-cached-read-is]

The platform invalidates cached responses when the data behind them changes. By default, a cached copy also expires five minutes after it is stored.

After you write, the platform makes your next reads wait for the cache to catch up with that write. This applies for a configurable window after you write, 60 seconds by default. A read in that window does not return a copy from before the write. If the catch-up cannot complete, the read skips the cache and carries no tag. This applies to reads by the same user who wrote. Other callers see the change when the cache invalidates.

`Cache-Control: private, no-cache` on a response the cache tags tells a client it can store the response but must revalidate before it reuses it. Revalidating means sending `If-None-Match`.

## Next steps [#next-steps]

- [Pagination](https://docs.settlemint.com/docs/api-reference/guides/pagination) for the page parameters that change the URL, and with it the cached response.
- [Errors](https://docs.settlemint.com/docs/api-reference/guides/errors) for the answers a poll can get besides `200` and `304`.
- [Versioning and deprecation](https://docs.settlemint.com/docs/api-reference/guides/versioning-and-deprecation) for how long a version stays supported.
- [Response caching in DALP 3.2](https://docs.settlemint.com/docs/changelog/dalp-3-2/developer-platform) for the release note.
