API

Backend

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.

FeatureRequest through LayerLayer adds
Branchingbranch_from_namespace or copy_from_namespace on a writeSource read check, embedding profiles, blobs and lineage follow the branch
Shardingsharding.num_shards on the first writeNothing. Not the same as Layer’s shards
PinningPATCH /v1/namespaces/{ns}/metadataWider scan fan-out on pinned namespaces
RecallPOST /v1/namespaces/{ns}/_debug/recallNothing

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), with schema, or with the other key gets 400 before anything reaches the store. Branch first, then write. On a store that cannot branch, the store’s 422 comes 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 403 naming 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 BranchAcrossStores naming 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:

StateOn a branch
Rows, schema, _hevlayer_* attributesCloned by Turbopuffer.
Embedding profiles for gateway-served embed attributesCopied, so the branch embeds query text like its source.
BlobsThe 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 historyStart empty. Any cache, snapshots or history left under the target’s name by a deleted namespace are cleared.
Write-triggered UDFs and PipelinesNot run. A branch is not a row write.
Index configFollows by name, not from the source.
Pinning, read_onlyTurbopuffer’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

esc