API
Blobs
Blobs store opaque bytes under their sha256 and serve them through the
gateway. A row never stores the bytes themselves. It stores an ordinary string
attribute such as image_blob: "blob://products/<sha256>".
Use blobs for media, source files or other binary payloads that rows point at but that should not ride the document write path.
Routes
| Route | Method | Behavior |
|---|---|---|
PUT /v1/namespaces/{ns}/blobs | PUT | Store raw bytes by sha256 and return a blob:// reference. |
GET /v1/namespaces/{ns}/blobs/{sha256} | GET | Serve the bytes by sha256. |
Where the bytes live
The namespace’s store holds the bytes when it has a native binary type.
Blobs larger than the store’s per-value cap, and every blob on a store without
one, go to the S3-compatible object store set by S3_BUCKET. The routes and
the blob:// reference are the same whichever backend holds the bytes.
| Store | Backend | Largest blob in the store |
|---|---|---|
| turbopuffer | A {ns}__hevlayer_blobs namespace, one row per sha256 with a bytes attribute | 6 MiB |
| Postgres (pgvector) | The layer_pgvector.blobs table, one bytea row per sha256 | 10 MiB (the gateway cap) |
| Stores without native bytes | S3 | None; every blob goes to S3 |
The gateway accepts blobs up to 10 MiB. turbopuffer caps a bytes value at
8 MiB of base64 on the wire, which is 6 MiB of blob. A blob between 6 MiB and
10 MiB on turbopuffer goes to S3.
With no S3_BUCKET, a blob that does not fit in the store is rejected rather
than dropped:
| Case | Status | error |
|---|---|---|
| Blob over 10 MiB | 413 | payload_too_large |
| Blob over the store’s cap, no S3 | 413 | blob_exceeds_store_cap |
| Store without native bytes, no S3 | 422 | object_store_not_configured |
A turbopuffer blob namespace appears in namespace listings next to the namespace it serves, and deleting a namespace does not delete its blobs. The per-store values are in the store capability matrix.
Store
with open("image.jpg", "rb") as f:
stored = await client.put_blob("products", f.read())
print(stored.ref)body, _ := os.ReadFile("image.jpg")
stored, err := client.PutBlob(ctx, "products", body, nil)import fs from "node:fs/promises";
const bytes = await fs.readFile("image.jpg");
const stored = await client.putBlob("products", bytes);curl -X PUT "$LAYER_GATEWAY_URL/v1/namespaces/products/blobs" \
-H "Authorization: Bearer $LAYER_GATEWAY_API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @image.jpg Response:
{
"ref": "blob://products/9f86d081884c7d659a2feaa0c55ad015...",
"sha256": "9f86d081884c7d659a2feaa0c55ad015...",
"size": 48213
}
The same bytes always return the same reference. The route rejects empty bodies and bodies over 10 MiB.
Write the returned ref as a normal row attribute:
{
"id": "B0123",
"vector": [0.1, 0.2],
"image_blob": "blob://products/9f86d081884c7d659a2feaa0c55ad015..."
}
The removed document blobs payload shape is still rejected. Binary bytes
do not traverse /v2/namespaces/{ns} writes.
Fetch
image = await client.get_blob("products", stored.sha256)image, err := client.GetBlob(ctx, "products", stored.Sha256)const image = await client.getBlob("products", stored.sha256);curl "$LAYER_GATEWAY_URL/v1/namespaces/products/blobs/$SHA256" \
-H "Authorization: Bearer $LAYER_GATEWAY_API_KEY" \
-o image.jpg Successful responses include immutable cache headers:
Cache-Control: public, max-age=31536000, immutable
ETag: "<sha256>"
The gateway sniffs common image types (jpeg, png, gif, webp) for
Content-Type; otherwise it returns application/octet-stream.
Warm Policy
Aerospike is the hot cache in front of whichever backend holds the bytes. A
cache miss reads the store, then S3, and backfills Aerospike best-effort.
PUT ...?warm=true writes one object through to cache.
For bulk warming, declare the row attributes containing blob references in the
Index’s spec.blobs.referenceAttributes, then call hint_cache_warm with
blobs=true and a positive blob_budget_bytes:
spec:
blobs:
referenceAttributes: [image_blob]
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"
This example warms only blobs, up to 100 MiB of cached bytes. The gateway scans
origin rows, follows same-namespace references in the declared attributes
(strings or arrays), and deduplicates objects. It stops before caching the first
object that would exceed the budget; the response reports blobs.budget_exhausted.
The budget bounds bytes written to cache, not reads from the store or S3:
concurrent reads may fetch beyond the cutoff. Missing objects and invalid
references are counted and skipped. See Warm cache
for SDK examples and response fields.