> ## Documentation Index
> Fetch the complete documentation index at: https://cortex-e852fafe-t3code-rewrite-docs-declutter.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-tenancy

> Keep each customer's and each user's data separate with databases and collections, and send the same scope on every write and read.

HydraDB scopes every write and every read with two fields: `database`, a separate workspace for a customer or an environment, and `collection`, a partition inside a database for one user, workspace, or team. Your backend picks both from the authenticated session; the end user never chooses them.

This guide builds up in order:

1. [Put each user in their own collection](#1-put-each-user-in-their-own-collection): one write and one read that carry the same `collection`.
2. [Choose a pattern for your app](#2-choose-a-pattern-for-your-app): B2C, B2B, and shared knowledge with personal memories.
3. [How writes and reads scope](#3-how-writes-and-reads-scope): the default collection, reading several collections, and deleting one.
4. [Migrate from the legacy tenant and sub-tenant fields](#4-migrate-from-the-legacy-tenant-and-sub-tenant-fields): `tenant_id`, `sub_tenant_id`, and the deprecation signals.
5. [Common mistakes](#common-mistakes) and [related pages](#related).

## 1. Put each user in their own collection

The example below saves a memory for the user `user_123` and then runs a personalized search for the same user. Both requests name the database `acme_corp` and the collection `user_123`. That is the whole mechanism: data written under a collection is read back only by requests that name that collection.

<Accordion title="Write and read one user's data: Python, TypeScript, or cURL">
  Set up your [SDK client](/api-reference/v2/sdks#client-setup) before running the Python or TypeScript example.

  <CodeGroup>
    ```python Python SDK theme={"dark"}
    import json
    import os
    from hydra_db import HydraDB

    client = HydraDB(token=os.environ["HYDRA_DB_API_KEY"])

    # 1. Write a memory under the user's collection.
    client.context.ingest(
        type="memory",
        database="acme_corp",
        collection="user_123",
        memories=json.dumps([
            {
                "text": "Prefers dark mode and short answers.",
                "infer": True,
                "user_name": "John",
            }
        ]),
    )

    # 2. Search knowledge and memories together, in the same collection.
    result = client.query(
        database="acme_corp",
        collection="user_123",
        query="refund policy",
        type="all",
    )
    ```

    ```typescript TypeScript SDK theme={"dark"}
    import { HydraDBClient } from "@hydradb/sdk";

    const client = new HydraDBClient({
      token: process.env.HYDRA_DB_API_KEY,
    });

    // 1. Write a memory under the user's collection.
    await client.context.ingest({
      type: "memory",
      database: "acme_corp",
      collection: "user_123",
      memories: JSON.stringify([
        {
          text: "Prefers dark mode and short answers.",
          infer: true,
          user_name: "John",
        },
      ]),
    });

    // 2. Search knowledge and memories together, in the same collection.
    const result = await client.query({
      database: "acme_corp",
      collection: "user_123",
      query: "refund policy",
      type: "all",
    });
    ```

    ```bash cURL theme={"dark"}
    # 1. Write a memory under the user's collection.
    curl -X POST 'https://api.hydradb.com/context/ingest' \
      -H "Authorization: Bearer $HYDRA_DB_API_KEY" \
      -H "API-Version: 2" \
      -F "type=memory" \
      -F "database=acme_corp" \
      -F "collection=user_123" \
      -F 'memories=[
        {
          "text": "Prefers dark mode and short answers.",
          "infer": true,
          "user_name": "John"
        }
      ]'

    # 2. Search knowledge and memories together, in the same collection.
    curl -X POST 'https://api.hydradb.com/query' \
      -H "Authorization: Bearer $HYDRA_DB_API_KEY" \
      -H "API-Version: 2" \
      -H "Content-Type: application/json" \
      -d '{
        "database": "acme_corp",
        "collection": "user_123",
        "query": "refund policy",
        "type": "all"
      }'
    ```
  </CodeGroup>
</Accordion>

### What each property means

| Property | What it is |
| - | - |
| `database` | The customer's or environment's workspace. Create it first with [`POST /databases`](/api-reference/v2/endpoint/create-tenant). |
| `collection` | The partition for this user inside the database. Both requests name the same one, so the query reads what the write stored. Leave it out and HydraDB uses the database's default collection, which is created on the first write. |
| `type` | On ingest, `memory` saves a [memory](/essentials/v2/memories). On query, `all` searches [knowledge](/essentials/v2/knowledge) and memories together and returns one ranked list; `knowledge` or `memory` searches one store. |
| `memories` | The memory items, as a JSON string. |
| `query` | The user's question. HydraDB ranks results inside the collection you named. |

Use stable internal IDs for both fields, such as `user_123`, `workspace_42`, or `org_acme`. Display names, emails, and user-typed labels change, and a changed scope name orphans the data written under the old one.

<a id="2-when-to-use-each" />

<a id="3-recommended-patterns" />

## 2. Choose a pattern for your app

Pick the scope by what you need to keep apart:

| Goal | Use |
| - | - |
| Separate customers on your platform | A different `database` per customer |
| Separate environments (`prod` vs `staging`) | A different `database` per environment |
| Separate per-user state within one customer | One `database`, `collection = user_id` |
| Separate per-workspace data within one customer | One `database`, `collection = workspace_id` |
| Store broadly shared knowledge | Omit `collection` to use the database's default collection |

Do not use `collection` to separate production from staging. Environments belong in separate databases.

### B2C application

Use one database for the application, and one collection per end user, for example `database = "acme_app"` and `collection = "user_123"`. Write each user's memories, preferences, and conversation history with that user's collection, and query with the same one. Keep shared knowledge outside the user collections.

### B2B SaaS

Use one database per customer organization, and collections for the workspaces, teams, projects, or users inside it, for example `database = "acme_corp"` and `collection = "workspace_42"`. Customer-wide knowledge goes in the customer's database. Workspace runbooks go in the workspace's collection. One user's memories go in that user's collection.

### Shared knowledge plus personal memories

For answers grounded in shared knowledge and personalized with the user's memories, send `type: "all"` on [`POST /query`](/api-reference/v2/endpoint/query). HydraDB searches the knowledge store (`vectorstore_status.knowledge`) and the memories store (`vectorstore_status.memories`) in parallel and returns one merged, re-ranked list. Section 1 does this.

`type: "all"` reads both stores from the same scope. If the shared knowledge lives in its own collection, such as `company_docs`, name both collections with `collections: ["company_docs", "user_123"]` instead of `collection`. HydraDB runs the query in each collection and merges the results. Every listed collection must already exist, and you can list up to 100.

When your prompt needs shared context and personal context formatted differently, call `POST /query` twice, once with `type: "knowledge"` and once with `type: "memory"`, and combine them in your application.

## 3. How writes and reads scope

Every write and read names a `database`, and may name a `collection`.

**Writes:** Send `collection` when the data belongs to one user, workspace, or team. Omit it only when you want the data in the database's default collection. A memory about John goes in John's collection. A workspace's runbooks go in that workspace's collection. Shared knowledge goes in whichever scope you will query it from.

**Reads:** A query that names one `collection` reads only that collection. It does not read the default collection or any other collection. To read several at once, send `collections` as a list, or as an object that maps each collection to a relative ranking weight, as in `{"company_docs": 1.5, "user_123": 0.8}`. Prefer one call with `collections` over several calls unless you need to format each scope's results differently.

**Filters inside a scope:** `collection` partitions the data. [Metadata filters](/essentials/v2/metadata) narrow results inside the partition, for example to one department or one status. Filters are not a replacement for choosing the right database and collection.

**Deleting a collection:** [`DELETE /databases/collections`](/api-reference/v2/endpoint/delete-collection) permanently removes one collection and everything in it. The parent database and its other collections stay. Confirm the name with [List Collections](/api-reference/v2/endpoint/list-sub-tenants) first. On the dashboard, open a database to see its collections and delete from the list.

<a id="7-migrating-from-the-legacy-tenant-and-sub-tenant-fields" />

## 4. Migrate from the legacy tenant and sub-tenant fields

`database` and `collection` are the current names for the fields that were called `tenant_id` and `sub_tenant_id`. The rename is user-facing only. Nothing about your stored data changes, the old names still work as deprecated aliases, and migration is optional and non-breaking.

Both names work on every surface:

| Surface | Legacy (still works) | Current (preferred) |
| - | - | - |
| Routes | `/tenants`, `/tenants/status`, `/tenants/{tenant_id}/metadata-schema`, and the rest | `/databases`, `/databases/status`, `/databases/{database}/metadata-schema`, and the rest |
| Request fields | `tenant_id`, `sub_tenant_id` | `database`, `collection` |
| `/query` multi-scope selector | `sub_tenant_ids` | `collections` |
| Query params | `?tenant_id=...&sub_tenant_id=...` | `?database=...&collection=...` |

Both route prefixes use the same handlers. Legacy fields are accepted in the query string, the JSON body, and the multipart form.

On `/query`, `collections` is the current way to scope to one or more collections, as a list or as an object mapping collection ID to a relative ranking weight. `sub_tenant_ids` remains accepted as its deprecated alias, and the singular `sub_tenant_id` still works too. See [Query](/api-reference/v2/endpoint/query).

### Deprecation signals

When a request uses a legacy route or a legacy field, HydraDB adds a migration nudge. It is never an error, and the status code is unchanged:

* **Response headers:** `Deprecation: true`, plus a `Warning` header carrying the migration message.
* **`meta.deprecation`:** a list of notices in the response envelope's `meta` object, on endpoints that return the envelope. Each entry carries `deprecated`, a readable `message`, and `deprecated_since` (`"2.0.1"`). Field-level notices, such as `sub_tenant_ids` to `collections`, also include `deprecated_field` and `preferred_field`; route-level notices omit them. A fully migrated request omits the list.

<Accordion title="Response envelope with a deprecation notice">
  ```json theme={"dark"}
  {
    "success": true,
    "data": { "...": "..." },
    "error": null,
    "meta": {
      "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
      "latency_ms": 12.3,
      "deprecation": [
        {
          "deprecated": true,
          "message": "The /tenants routes are deprecated. Migrate to the /databases routes.",
          "deprecated_since": "2.0.1"
        }
      ]
    }
  }
  ```
</Accordion>

Use these signals to find and retire legacy usage. Once you send only the current names on the `/databases` routes, the header and `meta.deprecation` disappear.

### Sending both names

If you send a current field and its deprecated alias together:

* **Same value:** for example `database` and `tenant_id` both `"acme"`. Accepted. HydraDB uses the current field.
* **Different values:** for example `database: "acme"` and `tenant_id: "other"`. Rejected with `400`, because the two names refer to the same thing and must agree. Send only one. The same rule applies to `collection` and `sub_tenant_id`, and to `collections` and `sub_tenant_ids` on `/query`.

## Common mistakes

**Mismatched `collection` between write and read:**
Data written under one collection does not surface when queried under another. Use the same `collection` for the same logical scope on every call.

**Sharing a `database` across environments:**
Do not use one database for both production and staging. Create a database per environment.

**Confusing `database` with `collection`:**
Use `database` for primary boundaries such as customers or environments. Use `collection` for partitions inside a database, such as users or workspaces.

**Writing shared knowledge under a user scope by accident:**
Knowledge written with one user's `collection` is invisible to every other user. Choose the write scope from where the content should be queried later.

## Related

* [Memories](/essentials/v2/memories): user-scoped context
* [Knowledge](/essentials/v2/knowledge): shared document context
* [Query](/essentials/v2/query): how scoping is applied at query time
* [Access Control](/essentials/v2/access-control): restricting who can retrieve a document inside a collection
* [How to Use API Results](/essentials/v2/api-results): merging query results into a prompt
* [Databases API](/api-reference/v2/endpoint/tenants-overview): creating databases and their metadata schema


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