API
Turbopuffer passthrough
These Turbopuffer features pass through Layer unchanged when the store is Turbopuffer, in Community Edition and Pro. Layer forwards the request and returns Turbopuffer’s response, errors included, so Turbopuffer’s documentation is the reference for each one. Each entry below says how to reach the feature through the gateway and what Layer adds, if anything.
| Feature | Request through Layer | Layer adds |
|---|---|---|
| Branching | branch_from_namespace or copy_from_namespace on a write | Source read check, embedding profiles, blobs and lineage follow the branch |
| Sharding | sharding.num_shards on the first write | Nothing. Not the same as Layer’s shards |
| Pinning | PATCH /v1/namespaces/{ns}/metadata | Wider scan fan-out on pinned namespaces |
| Recall | POST /v1/namespaces/{ns}/_debug/recall | Nothing |
We checked each request against a gateway on Turbopuffer on 2026-09-27.
Branching
A branch is an instant copy-on-write clone of a namespace. The source and the branch are independent afterwards, with no merge or diff.
curl -X POST "$LAYER_GATEWAY_URL/v2/namespaces/products-staging" \
-H "Authorization: Bearer $LAYER_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"branch_from_namespace": "products"}'
The value is the source namespace’s name, or the object
{"source_namespace": "products"} that turbopuffer-go sends. Layer accepts
both and forwards the body unchanged, so a Turbopuffer SDK pointed at the
gateway branches with no changes. copy_from_namespace takes the same forms
and makes a full, independent copy instead of a copy-on-write clone.
The request rules:
- Branch or copy, nothing else. A body that combines either key with
documents (
upsert_rows,upsert_columns,patch_rows,patch_columns,deletes,delete_by_filter,patch_by_filter), withschema, or with the other key gets400before anything reaches the store. Branch first, then write. On a store that cannot branch, the store’s422comes first. - The destination must be empty. Turbopuffer enforces this, and its error comes back unchanged.
- The key must be able to read the source. A branch or copy needs write
scope on the target and read scope on the source. Without read on the
source the gateway returns
403naming the source. No key is minted or widened for a branch: a key reaches the branch when its namespace globs match the branch’s name, so name branches into a family the key covers (wt-*). - One store. Source and target must resolve to the same VectorStore,
or the gateway returns
422 BranchAcrossStoresnaming both stores. To give a family of branches its own store, declare one Index for the family (see below).
After Turbopuffer accepts the branch, Layer carries the state it keeps outside the namespace:
| State | On a branch |
|---|---|
Rows, schema, _hevlayer_* attributes | Cloned by Turbopuffer. |
Embedding profiles for gateway-served embed attributes | Copied, so the branch embeds query text like its source. |
| Blobs | The source’s blob set is branched too, so the branch owns its bytes. Rows keep their blob://products/… references, and those resolve on the branch. |
| Cache and history | Start empty. Any cache, snapshots or history left under the target’s name by a deleted namespace are cleared. |
| Write-triggered UDFs and Pipelines | Not run. A branch is not a row write. |
| Index config | Follows by name, not from the source. |
Pinning, read_only | Turbopuffer’s rules: read_only is inherited, pinning is not. |
If a step after the branch fails, the gateway deletes the new branch and
returns the error, so a branch is never half made. Layer records the lineage
(products-staging ← products) in its object store; without S3_BUCKET no
lineage is recorded, and blob references that name the source namespace are
skipped by cache warm. S3 cannot branch, so a blob too
large for the store is read from the source’s S3 prefix. Layer never deletes
blobs today, so that read stays valid.
Index config for branches. An Index configures the namespaces its
spec.backend.namespace names. A trailing * makes it a pattern: one Index
with namespace: wt-* configures every branch named wt-…, including its
storeRef, facets and blob reference attributes. An exact name wins over a
pattern, and two overlapping patterns are rejected. A branch with no matching
Index runs on gateway defaults.
A copy (copy_from_namespace) follows the same rules and copies the same
state. A copy with source_api_key or source_region reads a namespace in
another organization or region; Turbopuffer authorizes it with the key in the
body, the gateway checks only write on the target, and blob:// references
to the source do not resolve in the copy.
Postgres and hev search have no native branch. They return
422 UnsupportedByStore: pgvector: branch_from_namespace (or search:), and
the gateway never emulates a branch with a copy. Check before branching with
GET /v2/namespaces/{ns}/capabilities, whose branch_from_namespace and
copy_from_namespace rows say what the namespace’s store supports, or see the
store capability matrix.
Upstream: turbopuffer.com/docs/branching
Sharding
Turbopuffer can split one namespace across several shards. Set the shard count on the namespace’s first write:
{
"sharding": {"num_shards": 2},
"upsert_rows": [{"id": 1, "vector": [0.1, 0.2, 0.3]}]
}
GET /v1/namespaces/{ns}/metadata then reports "sharding": {"num_shards": 2}.
The count is fixed after creation: resending the same value on a later write
is accepted, and a different value returns Turbopuffer’s 400.
This isn’t Layer’s sharding, and a namespace written through Layer has both kinds of field in its metadata:
Upstream: turbopuffer.com/docs/sharding
Pinning
Pinning keeps dedicated warm replicas of a namespace, billed separately by Turbopuffer. Turn it on with a metadata patch:
curl -X PATCH "$LAYER_GATEWAY_URL/v1/namespaces/products/metadata" \
-H "Authorization: Bearer $LAYER_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pinning": true}'
{"pinning": null} turns it off. Turbopuffer can refuse a pin when a region
has no capacity, and that 400 comes back unchanged. Readiness appears in
GET /v1/namespaces/{ns}/metadata under pinning.status.ready_replicas.
Layer reads pinning state to widen origin scan fan-out on pinned namespaces.
Upstream: turbopuffer.com/docs/pinning
Recall
The recall endpoint samples ANN queries against a namespace and compares them with exhaustive search, to measure how accurate the index is.
curl -X POST "$LAYER_GATEWAY_URL/v1/namespaces/products/_debug/recall" \
-H "Authorization: Bearer $LAYER_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"num": 3, "top_k": 2}'
{"avg_recall": 1.0, "avg_exhaustive_count": 2.0, "avg_ann_count": 2.0}
Turbopuffer bills the samples as queries.
Upstream: turbopuffer.com/docs/recall