> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyborg.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Query by Metadata

Find items by metadata alone — no query vector. With no `text`, the filter is resolved entirely against the encrypted metadata index and the matching items are returned unscored. With `text`, a BM25 full-text search runs over the index's `full_text` fields and the top matches are returned ranked by score; a filter given alongside restricts which items are scored. Works on untrained indexes. To add a vector leg on top of either form, use [`/v1/vectors/query`](./query) — see [Combining search legs](./query#combining-search-legs).

## Authentication

Required - API key via `X-API-Key` header:

```http theme={null}
X-API-Key: cyborg_your_api_key_here
```

Any valid credential is accepted — the root key or a per-user `cdbk_` token. When RBAC is not enabled (`CYBORGDB_SERVICE_ROOT_KEY` unset), no header is required.

## Request Body

```json theme={null}
{
  "index_name": "my_index",
  "index_key": "64_character_hex_string_representing_32_bytes",
  "filters": {"label": "dog"},
  "top_k": 10,
  "order_by": "views",
  "ascending": false
}
```

<Expandable title="parameters">
  <ParamField body="index_name" type="string" required="true">
    Name of the target index
  </ParamField>

  <ParamField body="index_key" type="string">
    32-byte encryption key as a hex string. Required for indexes created with the SDK-supplied KEK path; omit for KMS-backed indexes (the service resolves the key from the index's stored key envelope).
  </ParamField>

  <ParamField body="filters" type="object" default={{}}>
    Metadata filters as a JSON object; empty matches everything. Same operators as `/v1/vectors/query` except `$type`. See [Filter constraints](#filter-constraints) below.
  </ParamField>

  <ParamField body="top_k" type="integer">
    Cap on the number of IDs returned; omit for all matches. Applied **after** `order_by`, so it yields the first N of the sorted result.
  </ParamField>

  <ParamField body="order_by" type="string | object">
    Metadata field to sort matches by, applied post-filter. Accepts a field name, or a single-field MongoDB-style dict such as `{"views": -1}` (`1` ascending, `-1` descending) which overrides `ascending`. Unordered when omitted. Items missing the field, or holding a non-scalar, sort last. Not supported together with `text`.
  </ParamField>

  <ParamField body="ascending" type="boolean" default={true}>
    Sort direction when `order_by` is a field name. Ignored when `order_by` is omitted or is a dict (the dict's sign wins).
  </ParamField>

  <ParamField body="text" type="string">
    Query text for a BM25 full-text search. Requires an index with at least one `full_text` field. When set, results are ranked by descending BM25 `score` and `order_by` is not supported.
  </ParamField>

  <ParamField body="text_fields" type="array[string]">
    Which `full_text` fields the text search covers. Omitted means all designated fields. Naming a non-`full_text` field returns 400.
  </ParamField>

  <ParamField body="text_field_weights" type="array[number]">
    Per-field weights on the summed per-field BM25 scores, parallel to the searched fields. Omitted means 1.0 each.
  </ParamField>

  <ParamField body="require_all_terms" type="boolean" default="false">
    Require every query term to match (AND) instead of any (OR, the default).
  </ParamField>
</Expandable>

## Filter constraints

Unlike `/v1/vectors/query`, which falls back to a post-filter over the decrypted metadata when a filter cannot be answered from the index, `/v1/vectors/query_metadata` has no fallback stage — every filter leaf must be resolvable from the encrypted metadata index. That makes the index's [`metadata_schema`](../client/create-index#metadata-schema) enforcement instead of a performance hint:

* `$regex` / `$contains` require the field to have been declared `pattern: true` at index create time.
* A field declared `filterable: false` cannot be filtered on here at all.
* `$type` is not supported on this endpoint.

Any of these come back as 400 with the reason; use `/v1/vectors/query` with a vector for filters that need the post-filter path. Nested dot-paths (`loc.city`) are supported.

**Supported operators:** `$and`, `$or`, `$nor`, `$not`, `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$exists`, `$regex` (pattern fields only), `$contains` (pattern fields only).

## Response

**Filter-only query** — each result row is `{"id"}` (there is nothing to score):

```json theme={null}
{
  "results": [
    {"id": "item_1"},
    {"id": "item_3"},
    {"id": "item_7"}
  ],
  "ids": ["item_1", "item_3", "item_7"],
  "count": 3
}
```

**Text query** — each result row is `{"id", "score"}` in descending BM25 score order:

```json theme={null}
{
  "results": [
    {"id": "item_3", "score": 4.21},
    {"id": "item_1", "score": 2.87}
  ],
  "ids": ["item_3", "item_1"],
  "count": 2
}
```

<Expandable title="response fields">
  <ParamField body="results" type="array[object]">
    Matching items. On a `text` query each row is `{id, score}` in descending score order; on a filter-only query each row is `{id}` (no `score` key), following `order_by` when set, otherwise an unordered subset.
  </ParamField>

  <ParamField body="ids" type="array[string]">
    Matching item IDs, parallel to `results`. Retained for backward compatibility with callers that only read IDs.
  </ParamField>

  <ParamField body="count" type="integer">
    Number of items returned.
  </ParamField>
</Expandable>

## Exceptions

<AccordionGroup>
  <Accordion title="400 Bad Request">
    Filter references a field the metadata index cannot resolve — a `$regex`/`$contains` on a non-`pattern` field, a `filterable: false` field, or an unsupported operator. Also raised for text-parameter violations: `text` on an index with no `full_text` field, a `text_fields` entry that is not a `full_text` field, a text knob (`text_fields` / `text_field_weights` / `require_all_terms`) without `text`, or `order_by` combined with `text`
  </Accordion>

  <Accordion title="401 Unauthorized">
    Authentication failed (invalid API key) **or** wrong `index_key` on SDK-supplied indexes — see [error model](../introduction#error-model-api-keys-index-keys-and-kms)
  </Accordion>

  <Accordion title="404 Not Found">
    Index not found
  </Accordion>

  <Accordion title="422 Unprocessable Entity">
    Invalid request parameters
  </Accordion>

  <Accordion title="500 Internal Server Error">
    Internal server error
  </Accordion>
</AccordionGroup>

## Example Usage

**Filter only (all matches, unordered):**

```bash theme={null}
curl -X POST "http://localhost:8000/v1/vectors/query_metadata" \
     -H "X-API-Key: cyborg_your_api_key_here" \
     -H "Content-Type: application/json" \
     -d '{
       "index_name": "my_index",
       "index_key": "your_64_character_hex_key_here",
       "filters": {"label": "dog"}
     }'
```

**Combined operators with a result cap:**

```bash theme={null}
curl -X POST "http://localhost:8000/v1/vectors/query_metadata" \
     -H "X-API-Key: cyborg_your_api_key_here" \
     -H "Content-Type: application/json" \
     -d '{
       "index_name": "my_index",
       "index_key": "your_64_character_hex_key_here",
       "filters": {
         "$and": [
           {"label": "cat"},
           {"confidence": {"$gte": 0.9}}
         ]
       },
       "top_k": 10
     }'
```

**Sorted descending by a metadata field (top\_k applies after the sort):**

```bash theme={null}
curl -X POST "http://localhost:8000/v1/vectors/query_metadata" \
     -H "X-API-Key: cyborg_your_api_key_here" \
     -H "Content-Type: application/json" \
     -d '{
       "index_name": "my_index",
       "index_key": "your_64_character_hex_key_here",
       "filters": {"label": "dog"},
       "order_by": "views",
       "ascending": false,
       "top_k": 5
     }'
```

**Full-text search (index must have at least one `full_text` field; results ranked by BM25 score):**

```bash theme={null}
curl -X POST "http://localhost:8000/v1/vectors/query_metadata" \
     -H "X-API-Key: cyborg_your_api_key_here" \
     -H "Content-Type: application/json" \
     -d '{
       "index_name": "docs_index",
       "index_key": "your_64_character_hex_key_here",
       "text": "encrypted search",
       "filters": {"label": "article"},
       "top_k": 10
     }'
```

**Regex match (field must have been created with `pattern: true`):**

```bash theme={null}
curl -X POST "http://localhost:8000/v1/vectors/query_metadata" \
     -H "X-API-Key: cyborg_your_api_key_here" \
     -H "Content-Type: application/json" \
     -d '{
       "index_name": "docs_index",
       "index_key": "your_64_character_hex_key_here",
       "filters": {"title": {"$regex": "^intro"}}
     }'
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.