Skip to content
grepvine.aiJoin preview

Indexing

To index documents, you create an index and write documents into it. A document is searchable as soon as its write is acknowledged. The examples on this page build acme/support, an index of support tickets, and acme/monorepo, an index that mirrors a git repository.

The description is Markdown, and agents read it to decide whether an index is the one they need, so say what the index holds and how its keys are laid out. Concepts gives the rules for names, descriptions and tags.

import os
import grepvine
from grepvine.models import CreateIndexRequest
client = grepvine.Client(api_key=os.environ["GREPVINE_API_KEY"])
client.indexes.create("acme", body=CreateIndexRequest(
name="support",
description="Support tickets, one Markdown file per ticket, keyed `tickets/{yyyy}/{mm}/{id}.md`.",
))
client.indexes.wait_until_ready("acme", "support")

The curl examples on this page assume API=https://api.grepvine.ai/v1 and AUTH="Authorization: Bearer $GREPVINE_API_KEY".

Batches are the fast way to write. put_many, putMany and gv upload split any number of documents into requests of up to 2,048 documents and 16 MiB of text each, and return one result per document in input order. A result has a revision when the document was stored, or an error when it wasn’t.

from datetime import datetime, UTC
tickets = [
{
"key": "tickets/2026/10/T-48213.md",
"content": "# Checkout returns 502\n\nupstream timeout after 30000ms on POST /v2/orders\n",
"metadata": {
"product": "checkout",
"priority": 1,
"status": "open",
"opened_at": datetime(2026, 10, 14, 9, 12, tzinfo=UTC),
},
},
# ...
]
results = client.documents.put_many(
"acme", "support", tickets, idempotency_key="support-import-2026-10-14"
)
for result in results:
if result.error:
print(f"{result.key}: {result.error.code}")

Writing a key that already exists replaces that document. If one request of a multi-request call fails, the SDKs raise PartialBatchError with the results so far, the keys of the failed request and the keys never sent. To finish the call safely, repeat it with the same idempotency_key and the same documents. No document is written twice.

To upload files, point gv upload at a file, a directory or an S3 prefix. Each key is the file’s path relative to the source, after an optional key prefix:

Terminal window
gv upload --recursive ./runbooks --index acme/support runbooks/
gv upload --recursive s3://acme-exports/tickets/ --index acme/support
gv upload exports.zip --zip --index acme/support

Directory uploads skip what .gitignore excludes. gv upload never deletes documents.

A batch carries text only, so a binary document or text over 16 MiB needs a single-document write, of up to 1 GiB. Which content is searchable is set out in concepts. The single-document calls take metadata as a JSON string in the API’s typed form.

import json
from grepvine.models import File, PutDocumentForm
with open("exports/2026-10.csv", "rb") as payload:
client.documents.put("acme", "support", body=PutDocumentForm(
key="exports/2026-10.csv",
content=File(payload=payload, file_name="2026-10.csv"),
metadata=json.dumps({"source": {"type": "string", "value": "export"}}),
))

Metadata is what filters select on. Every value carries one of five types, and each client maps your values to them:

Type Python TypeScript gv put --metadata
string str string team=payments
integer int a whole number pages:integer=3
number float a number with a fraction score:number=0.91
boolean bool boolean generated:boolean=false
timestamp datetime, UTC when naive Date opened_at:timestamp=2026-10-14T09:12:00Z

In TypeScript, 4 becomes an integer and 4.5 a number. To store a whole value as a number, pass it tagged: { type: "number", value: 4 }. gv put --metadata-file reads a JSON object in the same tagged form.

Some clients add fields for you. Your own fields of the same name win.

Field Type Value Added by
path string The key. put_many, putMany, gv upload, gv sync
size integer Content length in bytes. put_many, putMany, gv upload, gv sync
mtime timestamp The file’s modification time. gv upload, gv sync

gv upload --records, gv put, Python’s documents.put and the REST API store only the fields you send.

A conditional write changes a document only if it is in the state you expect. Use if_none_match to create a key only if it doesn’t exist yet, and if_match with a revision to replace a document only if nobody changed it since you read it. In a batch, a document whose condition fails gets the error precondition_failed and the others are stored.

[created] = client.documents.put_many("acme", "support", [
{"key": "tickets/2026/10/T-48214.md", "content": draft, "if_none_match": True},
])
client.documents.put_many("acme", "support", [
{"key": created.key, "content": edited, "if_match": created.revision},
])

Guarantees gives the exact ordering rules.

A batch delete takes up to 2,048 keys, and each key succeeds or fails on its own.

from grepvine.models import DeleteObjectRecord, DeleteObjectsRequest
client.documents.delete("acme", "support", "tickets/2026/10/T-48213.md")
client.documents.delete_batch("acme", "support", body=DeleteObjectsRequest(objects=[
DeleteObjectRecord(key="tickets/2026/09/T-47001.md"),
DeleteObjectRecord(key="tickets/2026/09/T-47002.md"),
]))

gv sync makes an index match a directory. It uploads files that are new or changed and deletes documents whose files are gone.

Terminal window
gv sync . acme/monorepo --dry-run --json # one line per planned upload, delete or tag change
gv sync . acme/monorepo
  • In a git work tree, it uploads the tracked files that changed since the commit in the index’s git.commit tag, deletes the documents of files removed since then, and records the new commit and branch. Uncommitted changes to tracked files stop it before it writes anything.
  • Outside git, on the first sync, or when the recorded commit isn’t in the repository, it compares the whole directory with the whole index.
Terminal window
gv index info acme/support --json # document_count, content_bytes
gv list acme/support --prefix tickets/2026/ --delimiter / --json # browse keys like a directory

A listing marks each document searchable: true or searchable: false. After a large upload, gv index info reports optimization_progress_percent until background optimization is done. Search results stay complete in the meantime.