Skip to content

Caching

Every 2xx response carries an ETag. Cache the body against it and revalidate with If-None-Match.

Terminal window
# First fetch: keep the body and the ETag
curl -i https://app.simplifylocal.app/api/businesses/{slug}/jobs/house-washing-marietta \
-H "Authorization: Bearer sl_live_…"
# ETag: W/"3f9a…"
# Later: revalidate
curl -i https://app.simplifylocal.app/api/businesses/{slug}/jobs/house-washing-marietta \
-H "Authorization: Bearer sl_live_…" \
-H 'If-None-Match: W/"3f9a…"'
# HTTP/1.1 304 Not Modified (empty body; keep your copy)
  1. Send the ETag back exactly as received, including the W/ prefix and the quotes. Comparison is weak, so a value with or without W/ matches either way, but the quotes are part of the value.
  2. A 304 has no body. Serve the copy you kept. The ETag header is repeated on the 304.
  3. Do not cache beyond the ETag. /api/businesses/{slug}, /jobs and /jobs/{job_slug} send no Cache-Control on a normal response. Do not add a TTL of your own in front of them; revalidate on every render, or invalidate on webhooks.
  4. The build-time feed is the exception. /feed/jobs sends Cache-Control: public, max-age=300, s-maxage=600, stale-while-revalidate=86400, because its consumer is a build, not a live page.
  5. Never cache an error. Error responses send Cache-Control: no-store, max-age=0. In particular, do not let a 404 or 410 linger in a cache of your own.

If a response carries an X-SL-Stale-Ms header, part of its body was served from the last copy we read successfully, and the header’s value is that copy’s age in milliseconds. It is sent only during a temporary problem on our side, and such a response carries Cache-Control: public, max-age=0, s-maxage=30 instead of the endpoint’s usual header. Treat it as a normal 200; it is there so that you (or we) can tell the difference afterwards.

Subscribe to webhooks and invalidate the job’s URL, and the pages that list it, on every event. The guide Invalidate your CDN walks through it.