Skip to main content
You ingest context, HydraDB indexes it in the background, and retrieval finds the relevant passages when you query. Knowing which stage your context is in tells you what to check when a query comes back empty.

The big picture

The API has three jobs: Two details in the diagram:
  • Two stores, one ingest endpoint: POST /context/ingest routes to the Knowledge store when type=knowledge and to the Memories store when type=memory. Same endpoint, different bucket.
  • One query endpoint, every retrieval method: POST /query is the only search entry point. Its type field picks the store and query_by picks the matching method. Query covers every parameter.

Ingestion lifecycle

Ingestion is asynchronous. A successful upload means HydraDB accepted the work and queued it, not that it is searchable yet. Each source moves through these statuses: Poll GET /context/status with the returned id to follow each source, or register a webhook to be told instead. Two notes:
  • graph_creation is already searchable: chunks become retrievable as soon as embedding finishes. Wait for completed only when you need full graph traversal.
  • Failures come with a reason: an errored status carries an error_code and error_message, so you can tell a parse failure from a validation problem or an infrastructure issue.

From upload to query

The end-to-end flow, with the endpoint that owns each step:
  1. Create a database with POST /databases, optionally declaring a metadata schema up front.
  2. Wait for it to be ready by polling GET /databases/status until infra.ready_for_ingestion is true.
  3. Ingest context with POST /context/ingest: type=knowledge for documents and app sources, type=memory for preferences, decisions, and conversations.
  4. Wait for indexing by polling GET /context/status until each id reaches completed, or graph_creation if you do not need graph traversal.
  5. Query with POST /query, choosing type and the collection scope. Pass the result to your model with the helper in How to Use API Results.

Database isolation

A database (formerly tenant_id, still accepted as a deprecated alias) is the top-level isolation boundary: no database can read another database’s data. Within a database, a collection (formerly sub_tenant_id) is a partition. One collection holds shared knowledge; separate collections hold data that must never meet in one query, such as one end user’s memories. Omitting collection on any call resolves to the database’s default collection, which is created on the first write. Multi-tenancy covers when to create a new database rather than a new collection, and how scoping interacts with Memories and metadata filters.

Retrieval pipeline

Every POST /query call runs the same steps:
  1. Authenticate and scope: validate database and apply the requested collection or collections (the default collection when neither is sent).
  2. Filter before ranking: apply metadata_filters to narrow the candidate set.
  3. Retrieve: run hybrid retrieval over the semantic vector store and the BM25 keyword index, blended by alpha, or BM25 only when query_by: "text".
  4. Enrich: attach the relevant slice of the context graph when graph_context is on. In thinking mode, also expand the query, rerank, and pull in author-declared linked sources. mode: "auto", the default, picks fast or thinking per query.
  5. Shape the response: return ranked chunks, deduplicated sources, the graph_context slice, and any additional_context.
The response is retrieved context, not a model-written answer. You pass the chunks and any graph context into your own prompt.

When results are empty

A query is synchronous and reads only context that has finished indexing. When results look incomplete, walk the chain in order:
  1. Status first: did every id reach completed, or at least graph_creation? Check GET /context/status.
  2. Scope second: is the database correct? Did you write under one collection and read under another? See Multi-tenancy.
  3. Filters third: are you using the right namespace? Top-level metadata_filters keys match metadata; free-form fields belong under additional_metadata, and top-level filter keys must be declared in the database schema. See Metadata.