Skip to main content
The request fields are now named database (formerly tenant_id) and collection (formerly sub_tenant_id). The old names and the old /tenants routes remain fully supported as deprecated aliases, so existing integrations keep working without any changes — however, we recommend using the latest conventions. See Migrating from tenant_id and sub_tenant_id for the full compatibility contract.

1. What it is

HydraDB scopes data using two identifiers:
  • database - the top-level scoping identifier. Use it for customers, environments, or other primary data boundaries.
  • collection - an optional scoping identifier within a database. Use it for users, workspaces, teams, or other logical partitions.
HydraDB write and query operations are scoped by database. When collection is provided, it further narrows the scope for that operation. If you omit collection, HydraDB uses the database’s default collection. Use the same scoping values consistently across writes and reads. If you write data with one collection and later query with a different one, that data may not appear in the query results.

2. When to use each

Two practical rules:
  • Use a separate database whenever data must not mix across customers or environments.
  • Use a collection for logical partitions inside a database, such as users, workspaces, teams, or projects.
Do not use collection as a substitute for separate production and staging databases. Environments should usually be separated at the database level.

B2C application

Use one database for the application or customer account, and use each end-user as a collection.
Use this when each user has private Memories, preferences, or conversation history. Typical flow:
  • Write user Memories with collection = user_id.
  • Query user Memories with the same collection.
  • Keep shared Knowledge outside the user-specific scope.

B2B SaaS

Use one database per customer organization. Use collection for the workspace, team, project, or user scope inside that customer.
Typical flow:
  • Customer-level Knowledge uses the customer database.
  • Workspace-specific data uses a workspace collection.
  • User-specific Memories use a user-level collection.

Shared Knowledge + user personalization

For personalized answers grounded in shared Knowledge, use the type parameter on POST /query:
  • type: "knowledge" retrieves shared Knowledge (Knowledge vector store, vectorstore_status.knowledge).
  • type: "memory" retrieves user-specific Memories (Memories vector store, vectorstore_status.memories).
  • type: "all" runs both in parallel and returns one merged, re-ranked result set - usually what you want for personalized answers.
When you need different formatting for shared vs personal context in the LLM prompt, call POST /query twice (once with type: "knowledge", once with type: "memory") and combine in your application.

4. How scoping works

Writes

Writes include database, and may include collection. Use collection when the data belongs to a specific user, workspace, team, or other logical partition. Omit it when you intentionally want the data written to the database’s default collection. Examples:
  • A memory about John’s preferences should be written with John’s collection.
  • Workspace-specific runbooks should be written with that workspace’s collection.
  • Broadly shared Knowledge should use the same scope you plan to use when querying it.

Reads

Query requests include database, and may include collection. If collection is omitted, query uses the database’s default collection. Use the same scoping values on query that you used when writing the data. A query request with one collection should not be expected to retrieve data written under a different collection. If your application needs to combine data from multiple scopes, prefer one query call with collections unless you need separate response formatting or client-side treatment per scope. Use collection for partitioning data. Use metadata_filters for narrowing results within that scope.

Memories and Knowledge

Memories and Knowledge live in separate stores, both reached through POST /query:
  • type: "memory" retrieves Memories (user-scoped, personal).
  • type: "knowledge" retrieves Knowledge (shared documents and app sources).
  • type: "all" retrieves both in one call.
For a personalized answer, the common pattern is one POST /query with type: "all" and the user’s collection. HydraDB runs both stores in parallel and merges results. Use two separate calls only when you need to format the two streams differently in your LLM prompt.

Data written under one collection should not be expected to appear when querying from another. Keep scope identifiers stable and consistent in your application.


5. Minimal working example

The example below shows a common personalized-answer flow: write a user Memory with a single collection, then query Knowledge and Memories together with collections.

6. Common mistakes

Mismatched collection between write and read. Data written under one collection should not be expected to surface when queried under another. Use the same collection consistently for the same logical scope. Sharing a database across environments. Do not use one database for both production and staging. Create separate databases for separate environments. 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. Assuming query automatically searches every collection. A query call uses the scope you provide. If your application needs data from multiple scopes, pass collections as a list or weighted object. Writing shared Knowledge under a user scope by accident. If broadly shared Knowledge is written with a user-specific collection, it may not appear where other users expect it. Choose the write scope based on where the content should be queried later. Using metadata filters as a substitute for collections. Metadata filters narrow results inside a scope. They are not a replacement for choosing the right database and collection. Using unstable identifiers. Avoid display names, emails that may change, or user-provided labels as long-term scope identifiers. Prefer stable internal IDs such as user_123, workspace_42, or org_acme.

7. Migrating from the legacy tenant and sub-tenant fields

database/collection are the canonical v2 names for what were historically called tenant_id/sub_tenant_id. The rename is user-facing only: internally HydraDB still uses the historical names, so nothing about your data changes. The old names remain accepted forever; migration is optional and non-breaking. Backward compatibility is total across every surface: Both prefixes dispatch to the same handlers, and the legacy fields are accepted in the query string, JSON body, and multipart form. An existing integration that uses only the old names is a pure no-op and behaves exactly as before. On /query, collections is the canonical way to scope to one or more collections (send a list, or 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.

Deprecation signals

When a request uses a legacy route or a legacy field, HydraDB adds a non-breaking migration nudge (never an error, and the status code is unchanged):
  • Deprecation: true response header, plus a Warning header carrying the migration message.
  • meta.deprecation: a list of structured notices in the response envelope’s meta object (for endpoints that return the envelope). Each entry carries deprecated, a human-readable message, and deprecated_since (e.g. "2.0.1"); field-level notices (such as sub_tenant_idscollections) also include deprecated_field and preferred_field, while route-level notices omit them. Fully-migrated requests omit the list entirely.
Use these signals to find and retire legacy usage; once you send only the canonical names on the /databases routes, the header and meta.deprecation disappear.

Sending both names

If you send both a canonical field and its deprecated alias:
  • Same value (for example database and tenant_id both "acme"): accepted. HydraDB uses the canonical value.
  • Different values (for example database: "acme" and tenant_id: "other"): rejected with 400, since the two names refer to the same thing and must agree. Send only one. The same rule applies to collection/sub_tenant_id and to collections/sub_tenant_ids on /query.