Concepts
Grepvine has four nouns: an organization owns indexes, an index holds documents, and a query returns every line in an index that matches a pattern. Everything else in the API decides who may do what to them.
One API behind every client.
Section titled “One API behind every client.”Every client is a view of the same REST API at https://api.grepvine.ai/v1. The Python and TypeScript SDKs are generated from its OpenAPI spec, and gv and the web console call the same operations. The MCP server at https://api.grepvine.ai/mcp exposes seven tools built on them. Whichever client you use, a request gets the same roles, limits and audit trail.
flowchart LR py["Python SDK"] --> api["Grepvine API"]:::hl ts["TypeScript SDK"] --> api cli["gv CLI"] --> api mcp["MCP client"] --> api web["Web console"] --> api api --> idx["Your indexes"]
The SDKs and gv handle the request mechanics that the REST API leaves to the caller. If you call the API directly, you do these yourself:
| Mechanic | What the SDKs and gv do |
|---|---|
| Authentication | Send your API key as Authorization: Bearer gvk_…. |
| Idempotency | Give every write an Idempotency-Key of 16 to 128 characters and keep it the same across retries, so a retried write is applied once. |
| Slowing down | On a 429, wait for the interval in Retry-After before trying again. The SDKs wait up to a minute at a time and ten minutes in total per request. |
| Transient failures | The SDKs retry a 5xx answer or a dropped connection twice more with a short backoff. |
| Pagination | Follow pagination.next_page_id while pagination.has_next is true. paginate in both SDKs does this for you. |
Organizations.
Section titled “Organizations.”An organization owns indexes and has members. Its name shares one namespace with user names, so no organization can take a name a user already has, and the other way round. Every organization has exactly one owner.
A member’s role sets what they can do in that organization:
| Role | Can |
|---|---|
owner |
Everything, including transferring ownership and deleting the organization. |
admin |
Manage members other than the owner, create, change and delete indexes, and read and write all data. |
developer |
Read, write and delete documents, and search. |
reader |
Read and list documents, and search. |
An API key belongs to a user, not to an organization. A request made with it gets the user’s role in each organization it touches. gv login creates a key for you through a browser approval; the security page covers creating and revoking them.
Indexes.
Section titled “Indexes.”An index is a search space named {organization}/{index}, such as acme/contracts. A name is ASCII letters, digits and hyphens, and starts with a letter. It keeps the case it was created with but is matched without regard to case: once acme/MyIndex exists, acme/myindex addresses it and acme/myindeX can’t be created.
An index also carries a Markdown description of up to 16,384 characters and up to 64 key/value tags. An update merges the tags you send into the ones it has. gv sync uses the git.commit and git.branch tags to record which commit an index mirrors.
An index is always in one of four states:
| State | Meaning |
|---|---|
creating |
Just created. It is usually ready within a moment. wait_until_ready (Python) and waitUntilReady (TypeScript) wait for it. |
ready |
Accepts reads, writes and queries. |
deleting |
Deletion has started. The index no longer answers, and its storage is removed in the background. |
delete_failed |
Deletion hit an error. Grepvine keeps retrying it, and you can ask for the deletion again. |
Reading one index returns its current document_count and content_bytes. While the index is being optimized after a large write, the read also returns optimization_progress_percent. Storage and compute explains what that optimization does. A listing of indexes reports the same counts, but they can trail recent writes by a few seconds.
Documents.
Section titled “Documents.”A document has four parts. You write the key, the content and the metadata; Grepvine assigns the revision. Here is a support ticket:
{ "key": "tickets/2026/10/48213.md", "content": "# Refund stuck in pending\n\nThe customer was charged on 2026-10-21 ...", "revision": "9f2c41d07a3e", "metadata": { "product": {"type": "string", "value": "checkout"}, "priority": {"type": "integer", "value": 2}, "sentiment": {"type": "number", "value": -0.62}, "opened_at": {"type": "timestamp", "value": "2026-10-21T09:14:00Z"}, "escalated": {"type": "boolean", "value": true} }}| Part | What it is |
|---|---|
key |
The document’s name: a UTF-8, POSIX-style path of up to 1,024 bytes, with no empty, . or .. components. Listings return keys in byte order and can fold them into prefixes at a delimiter, like a file tree. |
content |
Up to 1 GiB. Content that is valid UTF-8 and at most 100 MiB is searchable. Anything else, such as an image a page links to, is stored, listed and served like the rest, but no query matches it, and a listing reports it with searchable: false. |
revision |
An opaque value that changes on every write. Pass it back as If-Match to write only if nothing changed since you read the document, or send If-None-Match: * to write only if the key doesn’t exist yet. |
metadata |
Up to 64 fields and 64 KiB encoded, for filtering searches. Fields have no schema, but every value has an explicit type: string, integer, number, timestamp or boolean. |
The same field can hold different types in different documents, and a filter comparison only matches a value of a compatible type. The SDKs infer each type from your language’s values.
Filters read metadata only, never the key. So that you can filter by location, the SDKs’ batch helpers and gv copy each document’s key into a path field and its size in bytes into a size field, unless you set them yourself.
You write documents one at a time, or in NDJSON batches of up to 2,048 documents and 16 MiB of text. Each document in a batch succeeds or fails on its own; guarantees gives the exact rules.
Queries.
Section titled “Queries.”A query runs one pattern against every searchable document in an index:
| Field | Meaning |
|---|---|
pattern |
Up to 8 KiB of UTF-8. |
mode |
regex (the default, RE2 syntax) or literal. |
case_sensitive |
true by default. |
whole_word |
Literal mode only. Letters, digits and underscore are word characters. |
multiline |
false by default, so the pattern applies to one line at a time. With true, it applies to the whole document and a match can span lines. |
filter |
An expression over metadata, up to 8 KiB, such as path STARTSWITH 'docs/' AND size < 102400. |
lines_before, lines_after |
Context lines around each result, 0 to 20 each. |
A result is one match, or several matches that share lines. It carries the document’s key and revision, the first and last line numbers it covers, up to 64 KiB of their text, the byte offset and length of each match, and any context lines you asked for. Results come back sorted by key, then line, then match offset, never by relevance.
Results arrive in pages of up to 200, 50 by default. A page can hold fewer results than you asked for, even none, while has_next is still true, so keep paging until it is false. Searching covers queries and filters in full.