API

Warm cache

Layer exposes two warm endpoints. hint_cache_warm is the turbopuffer-compatible hint; warm is the Layer-only shortcut that creates a gateway warm job.

Hint-cache warm

With no query parameters, the call is a raw passthrough: the gateway forwards it to turbopuffer unchanged and returns the upstream response verbatim. Existing turbopuffer clients keep their exact wire behavior.

curl "$LAYER_GATEWAY_URL/v1/namespaces/products/hint_cache_warm" \
  -H "Authorization: Bearer $LAYER_GATEWAY_API_KEY"

Supplying any warm option (turbopuffer, documents, snapshots, blobs, blob_budget_bytes, page_size) switches the call into Layer orchestration. Steps then default on for turbopuffer, documents, and snapshots; blobs default off:

StepWhat it does
turbopuffer=trueForwards the warm hint upstream.
documents=trueStarts an origin warm job to backfill the document cache.
snapshots=trueMirrors the latest S3 snapshot body into the cache.
blobs=trueHydrates declared blob references from S3 into Aerospike within blob_budget_bytes.
result = await client.hint_cache_warm(
    "products",
    turbopuffer=False,
    documents=False,
    snapshots=True,
)
const result = await client.hintCacheWarm("products", {
  turbopuffer: false,
  documents: false,
  snapshots: true,
});
curl "$LAYER_GATEWAY_URL/v1/namespaces/products/hint_cache_warm?turbopuffer=false&documents=false&snapshots=true" \
  -H "Authorization: Bearer $LAYER_GATEWAY_API_KEY"

The generated Go client omits false query parameters, so it cannot turn steps off — disable steps over REST (or the Python client) instead.

The orchestrated response reports per-step status:

{
  "namespace": "products",
  "turbopuffer": { "enabled": true, "status": "completed" },
  "documents": {
    "enabled": true,
    "status": "started",
    "job": { "id": "warm-job-uuid", "status": "running" }
  },
  "snapshots": {
    "enabled": true,
    "status": "completed",
    "key": "snapshots/products/...",
    "watermark_ms": 1715600400000,
    "sha": "..."
  }
}

If documents is enabled, the response includes a warm job; poll it through /warm-jobs/{id}.

Blob warming

Set blobs=true and supply a positive blob_budget_bytes. The Index must declare spec.blobs.referenceAttributes (for example, [image_blob]); see Blobs. A missing or zero budget, a budget without blobs=true, or an empty reference-attribute declaration is a validation error.

result = await client.hint_cache_warm(
    "products",
    turbopuffer=False,
    documents=False,
    snapshots=False,
    blobs=True,
    blob_budget_bytes=104857600,
)
const result = await client.hintCacheWarm("products", {
  turbopuffer: false,
  documents: false,
  snapshots: false,
  blobs: true,
  blobBudgetBytes: 104857600,
});
curl "$LAYER_GATEWAY_URL/v1/namespaces/products/hint_cache_warm?turbopuffer=false&documents=false&snapshots=false&blobs=true&blob_budget_bytes=104857600" \
  -H "Authorization: Bearer $LAYER_GATEWAY_API_KEY"

These examples warm only blobs. Go can enable blob warming with HintCacheWarmParams{Blobs: true, BlobBudgetBytes: 104857600}, but its omitted false values leave the other three steps enabled.

Blob warming finishes within the request. It scans origin rows, deduplicates same-namespace references, and stops before caching an object that would exceed the byte budget. Concurrent S3 reads can fetch beyond that cutoff, so the budget limits cache writes rather than network transfer. Missing objects and invalid references are skipped and counted.

The response’s blobs block reports attributes, budget_bytes, documents_scanned, refs_seen, objects, bytes, missing, invalid_refs, and budget_exhausted. Its status is completed even when the budget stops the scan; inspect budget_exhausted to distinguish that result. Unlike document warming, blob warming does not return a job to poll.

Layer warm

POST /v2/namespaces/{ns}/warm creates an asynchronous job that pages through turbopuffer, backfills Aerospike, and refreshes cache_warmed_through. Use it when bootstrapping a namespace whose data was written outside the gateway.

job = await client.warm_cache("products", page_size=1000)
job, err := client.WarmCache(ctx, "products", &hevlayer.WarmCacheParams{
    PageSize: 1000,
})
const job = await client.warmCache("products", { pageSize: 1000 });
curl -X POST "$LAYER_GATEWAY_URL/v2/namespaces/products/warm?page_size=1000" \
  -H "Authorization: Bearer $LAYER_GATEWAY_API_KEY"

The response is 202 Accepted with the warm job:

{
  "id": "warm-job-uuid",
  "namespace": "products",
  "status": "running",
  "progress": 0,
  "documents_scanned": 0,
  "created_at": "2026-05-26T10:00:00Z"
}

Poll it through:

job = await client.get_warm_job("products", job.id)
job, err := client.GetWarmJob(ctx, "products", jobID)
const job = await client.getWarmJob("products", jobId);
curl "$LAYER_GATEWAY_URL/v2/namespaces/products/warm-jobs/warm-job-uuid" \
  -H "Authorization: Bearer $LAYER_GATEWAY_API_KEY"

Cache-cold behavior

The split is deliberate. Fetch is correctness-first: a cache outage must not turn into a missing document. Warm is throughput-first: warming on a cold cache would be wasted work, so the gateway reports the cold state to the caller rather than silently no-op-ing.

A bare hint_cache_warm passthrough never touches the gateway cache, so it succeeds even while the cache is cold. The orchestrated form returns 503 cache_cold when documents, snapshots, or blobs is requested.

For how the cache recovers from an outage and the signals to watch, see the failure-mode runbook.

esc