Caching and ETags
Metadata and latest-value endpoints send an ETag. Send it back in If-None-Match and an unchanged answer costs a 304 with no body.
Conditional Requests
Endpoints that describe current state (metadata, listings, latest quotes, markets, venues, issuers) send a strong ETag computed from the representation. Store it with the body. Next time, send it back in If-None-Match: if nothing in the answer changed, the API replies 304 Not Modified with no body, and you reuse what you stored.
GET /v1/assets/listings/latest?convert=USD&limit=100 HTTP/1.1
Host: api.cantoncap.cc
X-API-Key: ccsk_...
HTTP/2 200
etag: "sha256-2kH9vYxq0Wf4mJr1sN8eTqLb3cZ6aUoP5dGiV7wXyE0"
cache-control: private, no-cache
vary: X-API-Key
GET /v1/assets/listings/latest?convert=USD&limit=100 HTTP/1.1
Host: api.cantoncap.cc
X-API-Key: ccsk_...
If-None-Match: "sha256-2kH9vYxq0Wf4mJr1sN8eTqLb3cZ6aUoP5dGiV7wXyE0"
HTTP/2 304
etag: "sha256-2kH9vYxq0Wf4mJr1sN8eTqLb3cZ6aUoP5dGiV7wXyE0"const cache = new Map(); // url -> { etag, body }
async function getCached(url) {
const hit = cache.get(url);
const response = await fetch(url, {
headers: {
"X-API-Key": process.env.CANTONCAP_API_KEY,
...(hit ? { "If-None-Match": hit.etag } : {}),
},
});
if (response.status === 304 && hit) return hit.body;
const body = await response.json();
if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
const etag = response.headers.get("etag");
if (etag) cache.set(url, { etag, body });
return body;
}What Changes the ETag
- The tag is computed from
dataonly, after the publication rules are applied. Thestatusblock (timestamps, request ID) changes on every answer and does not affect it. - A new price, a new timestamp, or a record published or withdrawn changes the tag. Internal age thresholds alone do not.
- The tag is strong.
If-None-Matchaccepts a single tag, a comma-separated list, a weak form (W/"...") of the same tag, or*.
304 is cheaper to send and to process, but it still counts as one request against your per-minute window. Conditional requests save bandwidth and parsing, not quota.Cache Headers
Cacheable answers carry Cache-Control: private, no-cache and Vary: X-API-Key. A browser or private cache may keep the answer but must revalidate it before use; a shared cache or CDN must not store it, because it is tied to a key. Several history endpoints send the same Cache-Control and Vary without accepting If-None-Match; the reference lists the headers of each endpoint. The issuer X feed answers Cache-Control: no-store and does not support conditional requests.
Endpoints with an ETag
GET /v1/simple/priceGET /v1/assets/mapGET /v1/assets/listings/latestGET /v1/assets/infoGET /v1/assets/{asset_id}GET /v1/assets/quotes/latestGET /v1/assets/market-pairs/latestGET /v1/marketsGET /v1/markets/{market_id}GET /v1/markets/{market_id}/tickerGET /v1/markets/{market_id}/orderbookGET /v1/markets/{market_id}/liquidityGET /v1/poolsGET /v1/pools/{pool_id}GET /v1/venuesGET /v1/venues/{venue_id}GET /v1/venues/{venue_id}/marketsGET /v1/venues/{venue_id}/capabilitiesGET /v1/issuersGET /v1/issuers/{issuer_id}GET /v1/global/metrics/latestGET /v1/issuers/{issuer_id}/evidenceGET /v1/issuers/{issuer_id}/relationships