Operations

Store capability matrix

Layer serves one API across its configured stores. Each cell below comes from the gateway’s backend capability declaration. Wire protocol matching explains the contract, and how we validate it is below the matrix. Pages with backend-specific content offer a picker when more than one documented backend applies.

Postgres configuration

Postgres uses VectorStore spec.kind: pgvector with a PostgreSQL connection URI in spec.endpoint.url. The standalone configuration covers standalone configuration, database authentication and extension versions, namespace-to-table mapping, and schema-driven HNSW, BM25, and scalar indexes. The quickstart runs the bundled database and gateway locally.

Read the Postgres column below for supported wire features and gaps. The write and query and fetch references describe Postgres limitations alongside the affected features. Text ranking follows the shared BM25-class scoring caveat.

A namespace may declare any number of full_text_search string attributes (see schema limits); a BM25 rank_by names one of them and scores that attribute only. Postgres keeps every full-text attribute on one pg_search BM25 index. Declaring a further full_text_search attribute on an existing namespace rebuilds that index inside the same write transaction, so the write takes time proportional to the namespace’s row count and the new attribute is rankable as soon as the write returns. Removing full_text_search from a declared attribute returns 400.

Matrix

Wire featureturbopufferPostgres
Namespace/schema CRUD and listing supportedsupported
Row upserts supportedsupported
Column upserts supportedsupported
Delete by ID supportedsupported
Single/batch fetch supportedsupported
Dense top-k (ANN) supportedsupported
Cosine / squared-Euclidean distance supportedsupported
BM25 text rank [1] supportedsupported
Explicit native Postgres text fallback unsupportedunsupported
HybridText rank operator (gateway dense + text RRF) supportedapproximateHybridText with fuzziness: 0 only (BM25 + dense legs, gateway RRF); auto/1/2 fuzziness and cursor/temporal_filter return 422
Attribute projection supportedsupported
Eq / NotEq / Gt / Gte / Lt / Lte / In; And / Or supportedsupported
Not / NotIn filters supportedsupported
Contains / ContainsAny filters supportedunsupported
Regex filters supportedunsupported
Other filter operators supportedunsupported
Fuzzy text filters supportedunsupported
Advanced text rank expressions supportedunsupported
Multi-vector ANN supportedunsupported
Sparse rank unsupportedunsupported
Multiple vector/text fields supportedapproximateAny number of full_text_search fields, ranked one at a time by BM25; a second vector field returns 422
Multi-query queries body / rerank_by (client-composed hybrid) supportedunsupported422 for a queries or rerank_by body; hybrid retrieval is the HybridText rank operator
Ranked cursor / searchAfter supportedunsupported
Ordered scans supportedsupported
Facets approximateNative wire request only; the optional portable adapter primitive is unavailable.unsupported
Aggregates / group_by supportedunsupported
Delete by filter approximateNative wire request only; the optional portable adapter primitive is unavailable.unsupported
Row patches supportedunsupported
Column patches supportedunsupported
Conditional writes supportedapproximateupsert_condition and delete_condition, including $ref_new; patch_condition returns 422 because row and column patches are unsupported
Copy namespace supportedunsupported
Branch namespace supportedunsupportedNo native namespace branching; the gateway does not emulate one.
Vendor encryption controls supportedunsupported
Arrow IPC import unsupportedunsupported
Export formats supportedunsupported
Backend warm hints supportedunsupported
Backend stability / watermark signals supportedunsupported
Backend snapshot integration supportedunsupported
UDF discovery / writeback primitives supportedunsupported
Arbitrary vendor administrative passthrough supportedunsupported
Embedding expressions / schema supportedapproximateGateway-resolved embedding only; one embedded attribute per namespace; chunked embedding returns 422.
Query by stored vector ID supportedunsupported
as_of / between filters supportedunsupported
Fused leg provenance supportedunsupported
Auto query routing supportedunsupported
Query scatter/gather supportedunsupported
Wire vector encoding supportedunsupported

[1] BM25-class scoring; tokenization differs; fused order may differ across backends

Schema limits

The schema_limits block of the capabilities report: whether a schema attribute may declare embed, and how many attributes of each indexed shape one namespace may declare. A blank count is no store-imposed limit.

LimitturbopufferPostgres
embed — schema attribute may declare embed supportedapproximateGateway-resolved embedding only; one embedded attribute per namespace; chunked embedding returns 422.
max_gateway_embed_attributes — attributes with a gateway-served embed 1
max_full_text_search_fields — full_text_search fields
max_vector_fields — vector fields ([N]f32) 1

Blobs

Where blob bytes live. A store with native bytes holds blobs up to max_value_bytes (blank is the gateway's 10 MiB cap); larger blobs, and every blob on a store without native bytes, go to S3.

BlobsturbopufferPostgres
native — the store holds blob bytes yesyes
max_value_bytes — largest blob in the store 6291456

Reading the cells

Supported means the declared feature is available. Approximate means it is served with the limits described in the cell. Unsupported means the store cannot serve that feature and returns 422 UnsupportedByStore. Capabilities describe individual features, not every combination of options. The schema limits table beneath the matrix gives the numeric bounds behind the multiple-fields row.

Two request shapes express hybrid retrieval, and each has its own row. A store accepts Supported routes and Approximate routes within their stated limits. Unsupported routes reject.

RouteRequestRow
HybridTextrank_by: [field, "HybridText", input] — the gateway runs one ranked query per leg and fuses with RRFHybridText rank operator
Multi-querya queries body — independent legs, fused by the store when rerank_by is setMulti-query

On Postgres, HybridText is approximate — served only with the shape its cell states (fuzziness: 0, no cursor or temporal filter) — and a queries or rerank_by body returns 422 UnsupportedByStore naming multi_query.

How we validate it

Matching the wire does not promise identical index internals, latency, scores, or ranking across stores. In particular, full-text ranking is backend-specific. Layer’s additional request fields, routes, and response metadata are documented as gateway enhancements.

  • API and client contracts. The SDK harness compares the gateway OpenAPI operations and generated Python client with the upstream API, and checks captured HTTP requests against documented examples using a mock server. These checks catch route, field, and serialization drift; they do not prove that a real backend returns the right results.
  • Backend acceptance. Store-specific suites send requests through a real gateway and backend using generated clients. They check supported operations and explicit rejection of unsupported requests. The Postgres suite runs against the Compose database.
  • Documented examples. A committed selection of upstream examples runs against a real gateway and store. Each request is classified as ok, unsupported, fail, or blocked by a prerequisite. A baseline change fails the check for review; matching a baseline can still preserve known failures. This is a selected test corpus, not proof that every upstream request or combination works. The observed results are at the end of this page.

The matrix is generated from backend declarations and checked for source drift. It states the contract; acceptance results are evidence of behavior. Both are needed to assess compatibility.

The fail-fast contract

Unsupported requests fail with 422 UnsupportedByStore. The gateway does not silently drop unsupported predicates or substitute a different backend. The body names the store, the route, and the rejected feature:

{
  "error": "UnsupportedByStore",
  "store": "pgvector",
  "route": "/v2/namespaces/traces",
  "feature": "patch_rows",
  "message": "UnsupportedByStore: pgvector: patch_rows"
}

feature is a stable identifier. Match on it; do not parse message.

  • A wire-feature id from the matrix when one owns the request: multi_query for a queries or rerank_by body, patch_rows, search_after for cursor or searchAfter, conditional_writes for a patch_condition on Postgres. The same string appears in the matrix and the 422.
  • Otherwise the rejected wire key, dotted when nested: a second embedded attribute on Postgres is schema.embed and a chunked one is embed.chunk; a filter operator is its name, such as Contains.
  • Schema-count limits use the limit name, such as max_vector_fields for a second vector attribute on Postgres.

message stays human-readable and starts with UnsupportedByStore: {store}: {feature}; any detail follows after a colon. feature is additive, so existing clients that read error and message are unaffected, and clients ignore values they do not know. A rejection that names no single feature carries no feature field.

from hevlayer import AsyncHevlayer, HevlayerError

try:
    await client.write_namespace("traces", body)
except HevlayerError as e:
    if e.error == "UnsupportedByStore" and e.feature == "multi_query":
        ...  # choose the HybridText route instead

Observed against the turbopuffer.com docs examples

The matrix above is what each backend declares. The table below is what happened when the committed selection of documented requests from turbopuffer.com/docs was run against a store through the gateway. Ok means the configured response checks passed (including the explicitly case-insensitive delete-status check). Unsupported means the store returned 422 UnsupportedByStore naming the feature. Fail means some other status, or a result that contradicts the docs; those are open findings, not declared gaps. Blocked means a prerequisite did not pass. The “Matrix row” column links each example to the declared cell it exercises. Regenerate with python3 tools/tpuf-docs-e2e/run.py --docs-json site/src/generated/tpuf-docs-e2e.json after a store change.

40 documented requests checked against a pgvector store on gateway 0.7.0-dev (2026-09-27): 13ok20unsupported2fail5blocked. 31 fixture requests (seed data the docs assume, and checks that a rejected write left no state) are listed separately.

Docs pageDocumented requestMatrix rowObserved
writedocumented upsert_rows + patch_rows + deleteswrite-upsert-rowpatch_rowsunsupportedUnsupportedByStore: pgvector: patch_rows
metadatadocumented GET metadatawrite-upsert-rownamespace_crudfailapprox_num_rows missing from response; schema.vector.ann.distance_metric missing from response
namespacesdocumented GET /v1/namespaces?page_size=50write-upsert-rownamespace_crudok
delete-namespacedocumented DELETE namespacewrite-upsert-rownamespace_crudoknote: status field is 'OK'; docs show 'ok'
writedocumented upsert_columns + patch_columns + deleteswrite-upsert-columnspatch_columnsunsupportedUnsupportedByStore: pgvector: patch_columns
writedocumented uuid id, FTS text, []uuid permissionswrite-schemaupsert_rowsunsupportedUnsupportedByStore: pgvector: schema.id
querypermissions ContainsAny (documented on docs/query, python only)write-schemaarray_filtersblockeddepends on step that ended 'unsupported': documented uuid id, FTS text, []uuid permissions
writedocumented two ANN vector columns ([3]f32, [2]f16)write-multivecmultiple_fieldsunsupportedUnsupportedByStore: pgvector: schema.ann
writedocumented seed upsert (drafts v2, v5)write-conditionalupsert_rowsok
writedocumented upsert_condition version Lt $ref_newwrite-conditionalconditional_writesok
writedocumented seed upsert (views 10, 2500)write-delete-by-filterupsert_rowsok
writedocumented delete_by_filter (IGlob + Lte)write-delete-by-filterdelete_by_filterunsupportedUnsupportedByStore: pgvector: delete_by_filter
writedocumented seed upsert (three published posts)write-patch-by-filterupsert_rowsok
writedocumented patch_by_filter (status -> archived)write-patch-by-filterpatch_rowsunsupportedUnsupportedByStore: pgvector: patch_by_filter
writedocumented copy_from_namespacewrite-copy-fromcopy_from_namespaceunsupportedUnsupportedByStore: pgvector: copy_from_namespace
branchingdocumented branch_from_namespacewrite-copy-frombranch_from_namespaceunsupportedUnsupportedByStore: pgvector: branch_from_namespace
api-overviewdocumented async copy (Prefer: respond-async)write-copy-fromcopy_from_namespaceunsupportedUnsupportedByStore: pgvector: copy_from_namespace
querydocumented ANN queryquery-vectordenseok
querydocumented ANN with And[timestamp Gte, public Eq] filtersquery-filtersscalar_filtersok
querydocumented BM25 with And[timestamp Gte, public Eq] filtersquery-fts-filteredftsok
querydocumented BM25 'quick fox' with include_attributesquery-fts-basicftsok
querydocumented In / Eq / NotGlob / Or[Glob] filters with exclude_attributesquery-complex-filteradvanced_filtersunsupportedUnsupportedByStore: pgvector: exclude_attributes
querydocumented exact kNN with category filterquery-knndenseunsupportedUnsupportedByStore: pgvector: kNN
querydocumented SparseKNN queryquery-sparse-vectorsparseunsupportedUnsupportedByStore: pgvector: SparseKNN
querydocumented attribute ordering (timestamp desc, Lt filter, limit 1000)query-orderingordered_scanok
querydocumented Count aggregate with filterquery-aggregatesaggregate_byunsupportedUnsupportedByStore: pgvector: aggregate_by
querydocumented Sum aggregate with filterquery-aggregatesaggregate_byunsupportedUnsupportedByStore: pgvector: aggregate_by
querydocumented Count grouped by color, sizequery-group-byaggregate_byunsupportedUnsupportedByStore: pgvector: aggregate_by
querydocumented Count grouped by ForEachUnique tagsquery-group-byaggregate_byblockeddepends on step that ended 'unsupported': fixture rows with []string tags for ForEachUnique (classifies the []string type)
querydocumented multi-query (queries array)query-multimulti_queryunsupportedUnsupportedByStore: pgvector: multi_query: wire key: queries
querydocumented multi-query with rerank_by RRF weightsquery-multimulti_queryunsupportedUnsupportedByStore: pgvector: multi_query: wire key: queries
querydocumented multi-vector (late interaction) ANNquery-late-interactionmultivectorblockeddepends on step that ended 'unsupported': fixture multi-vector column tokens: [][2]f32, ann (docs use [][128]f32)
querydocumented per-attribute limit ({per, total})query-limit-perdensefailHTTP 400: 'top_k must be between 1 and 10000'
ftsdocumented FTS-only write (no vector column)fts-basicupsert_rowsok
ftsdocumented BM25 'turbopuffer' (docs: ranking is [3, 1, 2])fts-basicftsok
ftsdocumented ContainsAllTokens 'search engine' (docs: [1, 2])fts-basicadvanced_filtersunsupportedUnsupportedByStore: pgvector: ContainsAllTokens
ftsdocumented write with FTS options on three fields (language, stemming, stopwords, case, []string)fts-advancedmultiple_fieldsunsupportedUnsupportedByStore: pgvector: schema.full_text_search: configuration object; only true/false is accepted
ftsdocumented Sum/Product weighted BM25 across fields with filtersfts-advancedadvanced_textblockeddepends on step that ended 'unsupported': documented write with FTS options on three fields (language, stemming, stopwords, case, []string)
ftsdocumented write with fuzzy + glob schema flagsfts-fuzzyfuzzyunsupportedUnsupportedByStore: pgvector: schema.fuzzy
ftsdocumented Sum[Product Glob, Fuzzy] rankfts-fuzzyfuzzyblockeddepends on step that ended 'unsupported': documented write with fuzzy + glob schema flags
Fixture requests (31)
ScenarioFixtureMatrix rowObserved
write-upsert-rowupsert_rows only (fixture: documented rows without the patch/delete legs)upsert_rowsunsupportedUnsupportedByStore: pgvector: schema.type: inferred from attribute my-string-array
write-upsert-rowdocumented rows minus the []string attribute (fixture)upsert_rowsok
write-upsert-rowlist with prefix (fixture: documented `prefix` parameter)namespace_crudok
write-upsert-rowmetadata after delete is 404 (fixture)namespace_crudok
write-upsert-columnsupsert_columns only (fixture)upsert_columnsunsupportedUnsupportedByStore: pgvector: schema.type: inferred from attribute my-string-array
write-upsert-columnsnull column value filters as missing (fixture)scalar_filtersblockeddepends on step that ended 'unsupported': upsert_columns only (fixture)
write-schemaBM25 over the documented text (fixture)ftsblockeddepends on step that ended 'unsupported': documented uuid id, FTS text, []uuid permissions
write-multivecANN on the named title_embedding column (fixture)multiple_fieldsblockeddepends on step that ended 'unsupported': documented two ANN vector columns ([3]f32, [2]f16)
write-conditionalrow count reflects the conditional outcome (fixture: 3 if applied, 2 if rejected)conditional_writesok
write-delete-by-filterrow count reflects the delete outcome (fixture: 1 if applied, 2 if rejected)delete_by_filterok
write-patch-by-filterarchived rows reflect the patch outcome (fixture: 2 if applied, 0 if rejected)patch_rowsok
write-copy-fromsource namespace (fixture)upsert_rowsok
query-vectorfixture rows matching the documented response ($dist 0.0 and 2.0)upsert_rowsok
query-filtersfixture rows with datetime timestamp, public flag, title (classifies the documented datetime schema type)upsert_rowsunsupportedUnsupportedByStore: pgvector: schema.type: datetime
query-filtersfixture rows with timestamp as plain string; datetime type is classified separately, public flag, titleupsert_rowsok
query-fts-filteredfixture rows with FTS content, datetime timestamp, public flag (classifies the documented datetime schema type)upsert_rowsunsupportedUnsupportedByStore: pgvector: schema.type: datetime
query-fts-filteredfixture rows with FTS content, timestamp as plain string; datetime type is classified separately, public flagupsert_rowsok
query-fts-basicfixture rows with FTS content and titleupsert_rowsok
query-complex-filterfixture rows with key1 and filenameupsert_rowsok
query-knnfixture rows with categoryupsert_rowsok
query-sparse-vectorplain fixture namespace (sparse schema not fixtured; classifies the query path only)upsert_rowsok
query-orderingfixture rows with datetime timestamp (no vector) (classifies the documented datetime schema type)upsert_rowsunsupportedUnsupportedByStore: pgvector: schema.type: datetime
query-orderingfixture rows with timestamp as plain string; datetime type is classified separately (no vector)upsert_rowsok
query-aggregatesfixture rows with cool_scoreupsert_rowsok
query-group-byfixture rows matching the documented groups (blue/XL x1, red/L x2)upsert_rowsok
query-group-byfixture rows with []string tags for ForEachUnique (classifies the []string type)upsert_rowsunsupportedUnsupportedByStore: pgvector: schema.type: inferred from attribute tags
query-multifixture rows with vector and FTS attr1upsert_rowsok
query-multigateway hybrid over the same legs with the documented `limit` alias (fixture)hybridunsupportedUnsupportedByStore: pgvector: limit
query-multigateway hybrid over the same legs with top_k (fixture: Layer's fused route, RFC 0114 phase one)hybridok
query-late-interactionfixture multi-vector column tokens: [][2]f32, ann (docs use [][128]f32)multivectorunsupportedUnsupportedByStore: pgvector: schema.ann
query-limit-perfixture rows across two categoriesupsert_rowsok
esc