Caching & conditional requests
Every GET response from the Bordio API carries an ETag. By sending it back on your next request you can ask “has this changed?” and get a tiny 304 Not Modified instead of the full body when nothing has changed.
Why it exists
Section titled “Why it exists”Most data you read — task lists, definitions, custom fields — changes far less often than you poll it. Without caching, every GET re-downloads an identical body. Conditional requests let you:
- Save bandwidth — an unchanged resource comes back as an empty
304instead of the full payload. - Revalidate cheaply — one round-trip confirms your cached copy is still current.
- Simplify cache logic — the server tells you when to refresh; you never guess.
How the server behaves
Section titled “How the server behaves”On every GET, the API sets two headers:
ETag: "e3b0c44298fc1c149afbf4c8996fb924"Cache-Control: private, no-cache- The
ETagis a strong validator computed over the exact response body — if the body changes in any way, theETagchanges. Cache-Control: private, no-cachemeans: you may store the response, but you must revalidate it with the server before reusing it (don’t serve it blind), and it is private to this key — never cache it in a shared/CDN cache.
This applies to all GET endpoints — tasks, subtasks, events, projects, members and every definitions resource (task statuses, types, custom fields, tags…).
How to use it
Section titled “How to use it”- On a
GET, store the response body together with itsETag. - On your next
GETof the same resource, send the stored value in anIf-None-Matchheader. - The server compares it to the current
ETag:- Match →
304 Not Modifiedwith an empty body. Reuse your cached copy. - No match →
200 OKwith the new body and a newETag. Replace your cached copy and storedETag.
- Match →
# First request — note the ETagcurl -i "https://api.bordio.com/public/v1/custom_fields?project_id=proj_6594a1b2c3d4e5f6a7b8c9d0" \ -H "Authorization: Bearer brd_sk_live_..."# → 200 OK# ETag: "e3b0c44298fc1c149afbf4c8996fb924"
# Later — revalidate with the stored ETagcurl -i "https://api.bordio.com/public/v1/custom_fields?project_id=proj_6594a1b2c3d4e5f6a7b8c9d0" \ -H "Authorization: Bearer brd_sk_live_..." \ -H 'If-None-Match: "e3b0c44298fc1c149afbf4c8996fb924"'# → 304 Not Modified (no body — your cached copy is still current)A minimal client cache:
const cache = new Map(); // url -> { etag, body }
async function getCached(url, apiKey) { const prev = cache.get(url); const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}`, ...(prev ? { 'If-None-Match': prev.etag } : {}), }, });
if (res.status === 304) return prev.body; // unchanged — reuse cache
const body = await res.json(); cache.set(url, { etag: res.headers.get('ETag'), body }); return body;}Weak comparison
Section titled “Weak comparison”If-None-Match is compared using the weak comparison function (RFC 7232 §3.2), so revalidation is forgiving about the weak-validator prefix:
- A
W/prefix on either side is ignored —W/"abc"and"abc"are treated as the same tag. - You may send a comma-separated list of tags; a match on any one satisfies the condition:
If-None-Match: "abc", W/"def". - You may send
*, which matches any current representation.
You normally just echo back the exact ETag string you received — the weak comparison simply means a W/-prefixed tag from your HTTP client still matches.
Things to keep in mind
Section titled “Things to keep in mind”- Treat the
ETagas opaque. It’s a hash — don’t parse it, compare only for equality, store it verbatim. - A
304still counts as a request against your rate limit and usage. Conditional requests save bandwidth, not your request budget — so keep a sensible polling interval. ETagis per representation. Different query parameters (e.g. a differentproject_id, page cursor, or filter) produce different bodies and therefore differentETags. Cache them per full URL.- Conditional requests apply to reads only (
GET). Writes (POST/PATCH/DELETE) are never conditional here — for safe retries of writes, use idempotency keys instead.