API

Write & Stage

Layer stamps every row-producing write with _hevlayer_upserted_at for supported stores. The stamp is what holds the read watermark; the full set of reserved attributes Layer manages on a row lives in the document model.

To branch or copy a namespace, see Branching.

Upsert and delete

POST /v2/namespaces/{namespace} accepts a JSON body. The first schema write creates the namespace. With the common client:

await client.write_namespace("preview_docs", {
    "distance_metric": "cosine_distance",
    "schema": {
        "vector": {"type": "[3]f32"},
        "body": {"type": "string", "full_text_search": True},
        "category": {"type": "string"},
        "year": {"type": "int"},
    },
    "upsert_rows": [{"id": "atlas", "vector": [1, 0, 0],
                     "body": "moon geology", "category": "space", "year": 2026}],
})
_, err := client.WriteNamespace(ctx, "preview_docs", hevlayer.TurbopufferWriteRequest{
    "distance_metric": "cosine_distance",
    "schema": map[string]any{
        "vector": map[string]any{"type": "[3]f32"},
        "body": map[string]any{"type": "string", "full_text_search": true},
        "category": map[string]any{"type": "string"},
        "year": map[string]any{"type": "int"},
    },
    "upsert_rows": []map[string]any{{"id": "atlas", "vector": []float64{1, 0, 0},
        "body": "moon geology", "category": "space", "year": 2026}},
})
await client.writeNamespace("preview_docs", {
  distance_metric: "cosine_distance",
  schema: {
    vector: { type: "[3]f32" },
    body: { type: "string", full_text_search: true },
    category: { type: "string" }, year: { type: "int" },
  },
  upsert_rows: [{ id: "atlas", vector: [1, 0, 0],
    body: "moon geology", category: "space", year: 2026 }],
});
curl --fail-with-body "$LAYER_GATEWAY_URL/v2/namespaces/preview_docs" \
  -H "Authorization: Bearer $LAYER_GATEWAY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"distance_metric":"cosine_distance","schema":{"vector":{"type":"[3]f32"},"body":{"type":"string","full_text_search":true},"category":{"type":"string"},"year":{"type":"int"}},"upsert_rows":[{"id":"atlas","vector":[1,0,0],"body":"moon geology","category":"space","year":2026}]}'

Delete by string ID using the same route:

await client.write_namespace("preview_docs", {"deletes": ["atlas"]})
await client.writeNamespace("preview_docs", { deletes: ["atlas"] });
curl --fail-with-body "$LAYER_GATEWAY_URL/v2/namespaces/preview_docs" \
  -H "Authorization: Bearer $LAYER_GATEWAY_API_KEY" \
  -H 'Content-Type: application/json' -d '{"deletes":["atlas"]}'

Patches, conditions and column writes

On other stores, write-shape support follows the backend notes above. Use patch_rows to modify named fields without replacing a complete document only when the selected store supports patches.

Status

Layer validates the body before forwarding and can fail independently of the backing store, so the write path carries a few statuses a plain proxy wouldn’t:

  • 200 OK — applied to the selected store. Gateway bookkeeping depends on the backend.
  • 422 Unprocessable Entity — Layer rejected the body before forwarding: no recognized native write operation, a reserved _hevlayer_* attribute name, a removed custom-write key, or a schema type the configured store cannot represent. The body is a Layer error ({ "error": "validation_error", … } or { "error": "UnsupportedByStore", … }), not a store one.
  • Upstream passthrough — any non-2xx the backing store returns is relayed verbatim.
  • 502 Bad Gateway — Layer could not reach the backing store ({ "error": "upstream_error", … }); the write did not apply.

The passthrough case includes a failed conditional write (upsert_condition, patch_condition, delete_condition) — turbopuffer’s error body comes back untouched.

Stage

Stage caches a document before it’s upserted into your vector store. That O(1) read/write is especially useful for queuing chunks in a two-stage pipeline, where a CPU worker stages chunks and a GPU worker reads them back to write vectors. Staged documents are ephemeral until they’re upserted, though — a Layer document cache outage loses anything still staged.

await client.put_pipeline_document_chunks("product-images", "asin-B08N5WRWNW", {
    "chunks": [
        {"id": "asin-B08N5WRWNW-0", "text": "Wireless noise-cancelling headphones"},
        {"id": "asin-B08N5WRWNW-1", "text": "40-hour battery life", "metadata": {"page": 2}},
    ],
})
client.PutPipelineDocumentChunks(ctx, "product-images", "asin-B08N5WRWNW", &hevlayer.PutChunksRequest{
    Chunks: []hevlayer.Chunk{
        {ID: "asin-B08N5WRWNW-0", Text: "Wireless noise-cancelling headphones"},
        {ID: "asin-B08N5WRWNW-1", Text: "40-hour battery life", Metadata: map[string]interface{}{"page": 2}},
    },
})
await client.putPipelineDocumentChunks("product-images", "asin-B08N5WRWNW", {
  chunks: [
    { id: "asin-B08N5WRWNW-0", text: "Wireless noise-cancelling headphones" },
    { id: "asin-B08N5WRWNW-1", text: "40-hour battery life", metadata: { page: 2 } },
  ],
});
curl -X PUT "$LAYER_GATEWAY_URL/v2/pipelines/product-images/documents/asin-B08N5WRWNW" \
  -H "Authorization: Bearer $LAYER_GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chunks": [
      {"id": "asin-B08N5WRWNW-0", "text": "Wireless noise-cancelling headphones"},
      {"id": "asin-B08N5WRWNW-1", "text": "40-hour battery life", "metadata": {"page": 2}}
    ]
  }'

Staging stores chunks in the Aerospike document cache and marks the document pending. Re-staging the same document ID replaces the chunks and resets state to pending. The full pipeline API is documented under Pipelines.

esc