---
title: Indexing documents
description: Put, read and delete one document in a search index, with the version fence that makes a write conditional.
type: how-to
product: search
summary: The three calls that put a document in an index, take it back and remove it.
updated: 2026-09-28
order: 0
---

A document lives in one index, at one address, and the two ids in the URL are
what make that address unique.

<EndpointTable
	endpoints={[
		{
			method: 'PUT',
			path: '/v1/documents/{index_id}/{document_id}',
			description: 'Writes a document to a search index, replacing the current version.',
		},
		{
			method: 'GET',
			path: '/v1/documents/{index_id}/{document_id}',
			description: 'Reads a document.',
		},
		{
			method: 'DELETE',
			path: '/v1/documents/{index_id}/{document_id}',
			description: 'Deletes a document.',
		},
	]}
/>

All three are served at `https://api.data.sylphx.com`. `index_id` is the id
segment of the index's name in the environment, and `document_id` is the
document's own id; there is no CLI command for a document, so these are API
calls. `PUT` and `DELETE` need `data:write`; `GET` needs `data:read`.

## The document id is yours

Nothing assigns a document id. You write one, and writing the same id again
replaces what is at that address, so the id is the part to get right: it is
the address the index retrieves the document by, and the address a delete
names. An id you choose is also the only key you need — a document's contents
can change under a stable id without anything else knowing.

## Writing one

```bash
curl -X PUT "https://api.data.sylphx.com/v1/documents/index-id/document-id" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Three fields carry the work:

<PropertyTable
	properties={[
		{
			name: 'document_json',
			type: 'bytes',
			description: 'The document as JSON, base64; at most 1 MiB.',
		},
		{
			name: 'vector',
			type: 'double[]',
			description: 'The document’s embedding, at most 4096 numbers; the index’s vector dimensions when it has them.',
		},
		{
			name: 'expected_version',
			type: 'uint64',
			description: 'Write only when the current version equals this one (0: any).',
		},
	]}
/>

The write is a replace, not a merge: what the address held is gone, and
`version` moves on to the next one. That is why `expected_version` earns its
place when two writers can reach the same document — a write that names the
version it read is refused instead of quietly overwriting a version someone
else wrote since.

The answer is the stored `document`:

<PropertyTable
	properties={[
		{
			name: 'document_json',
			type: 'bytes',
			description: 'The document as JSON, base64.',
		},
		{
			name: 'vector',
			type: 'double[]',
			description: 'The document’s embedding; its length is the index’s vector dimensions.',
		},
		{
			name: 'sha256',
			type: 'string',
			description: '`sha256:<hex>` of the document.',
		},
		{
			name: 'version',
			type: 'uint64',
			description: 'The version, increasing per document.',
		},
		{
			name: 'updated_at_unix_ms',
			type: 'int64',
			description: 'When this version was written, in Unix milliseconds.',
		},
	]}
/>

## Embeddings are yours to supply

Data never invents embeddings. In an index with dimensions, a document carries
the embedding you wrote with it, and a vector query matches against that
vector rather than against anything the index derived. So the model that
produces the embeddings is a decision you keep: an embedding written by one
model and a query embedded by another are two different spaces, and the
nearness between them means nothing.

## Reading one back

```bash
curl "https://api.data.sylphx.com/v1/documents/index-id/document-id" \
  -H "Authorization: Bearer $SYLPHX_API_KEY"
```

The answer is the whole stored record, `version` included. A read is what a
conditional write starts from: take `version` from here, and pass it as
`expected_version` there.

## Deleting one

```bash
curl -X DELETE "https://api.data.sylphx.com/v1/documents/index-id/document-id" \
  -H "Authorization: Bearer $SYLPHX_API_KEY"
```

The answer's `deleted` says whether the document existed, and the query takes
`expected_version` for the same reason the write does. A document delete
removes that document and nothing else — the index keeps its name, its
settings and its other documents, and deleting the index is a separate call.

<RelatedDocs
	links={[
		{
			href: '/docs/search/querying',
			label: 'Querying',
			description: 'How a text or vector query finds the documents you wrote.',
		},
		{
			href: '/docs/api/documents',
			label: 'The documents collection',
			description: 'Every method, its scope and its examples.',
		},
		{
			href: '/docs/search/quickstart',
			label: 'Quickstart',
			description: 'Create an index and write the first document.',
		},
	]}
/>
