Metadata-Version: 2.4
Name: hydradb-sdk
Version: 0.0.5
Summary: The official Python SDK for the Hydra DB (hydradb.com)
Author-email: Nishkarsh Srivastava <nishkarsh@hydradb.com>
License: Copyright (c) 2026 Hydra DB
        
        All Rights Reserved.
        
        PROPRIETARY AND CONFIDENTIAL
        
        This software is the proprietary and confidential property of AGI Context, INC ("the Company").
        Permission is hereby granted to users to install and use this software as part of the Hydra DB service, subject to the terms and conditions of the service agreement entered into with the Company.
        
        You may not, without the express written permission of the Company:
        
        1. Copy, modify, or create derivative works of the software.
        2. Distribute, sell, rent, lease, sublicense, or otherwise transfer the software to any third party.
        3. Reverse engineer, decompile, or disassemble the software, except and only to the extent that such activity is expressly permitted by applicable law notwithstanding this limitation.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
Project-URL: Homepage, https://hydradb.com/
Project-URL: Documentation, https://docs.hydradb.com/
Keywords: hydradb-sdk,hydradb,ai,sdk,api,generative ai,rag,db
Classifier: Development Status :: 5 - Production/Stable
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.24
Requires-Dist: pydantic<3,>=1.10
Dynamic: license-file

# HydraDB Python SDK

The official Python SDK for [HydraDB](https://hydradb.com), a memory and retrieval infrastructure for AI applications.

The SDK exposes synchronous and asynchronous clients for tenant management, file ingestion, memory ingestion, recall, fetch/list APIs, deletion, API key creation, graph health, and raw embedding workflows.

Documentation: [docs.hydradb.com](https://docs.hydradb.com)

---

## Installation

Install the package from PyPI:

```bash
pip install hydradb-sdk
```

Import the SDK using the Python package name:

```python
from hydra_db import HydraDB, AsyncHydraDB
```

> Note: The package name is `hydradb-sdk`, but the import name is `hydra_db`.

---

## Client setup

```python
import os
from hydra_db import HydraDB

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

The default API base URL is:

```text
https://api.hydradb.com
```

For local development, pass `base_url` directly:

```python
from hydra_db import HydraDB

client = HydraDB(
    token="YOUR_API_KEY",
    base_url="http://localhost:8080",
)
```

Async client:

```python
import os
from hydra_db import AsyncHydraDB

async_client = AsyncHydraDB(token=os.environ["HYDRA_DB_API_KEY"])
```

---

## Important tenant and sub-tenant rule

Most methods accept both `tenant_id` and `sub_tenant_id`.

If you upload with a `sub_tenant_id`, you should also verify, recall, fetch, and delete with the same `sub_tenant_id`.

```python
TENANT_ID = "my-company"
SUB_TENANT_ID = "my-sub-tenant"
```

Do not remove `sub_tenant_id` during verification unless you intentionally uploaded into the default sub-tenant. Omitting `sub_tenant_id` means HydraDB will use the default sub-tenant for that tenant.

---

## Tenant management

### Create a standard tenant

```python
response = client.tenant.create(tenant_id="my-company")
print(response)
```

### Create a tenant for raw embeddings

Use this when you want to bring your own embeddings instead of using HydraDB file/memory ingestion.

```python
response = client.tenant.create(
    tenant_id="my-embeddings-tenant",
    is_embeddings_tenant=True,
    embeddings_dimension=1536,
)
```

### Create a tenant with metadata schema

```python
response = client.tenant.create(
    tenant_id="my-company",
    tenant_metadata_schema={
        "department": "string",
        "region": "string",
    },
)
```

### List tenant IDs

```python
tenants = client.tenant.get_tenant_ids()
print(tenants.tenant_ids)
```

### List sub-tenant IDs

```python
sub_tenants = client.tenant.get_sub_tenant_ids(tenant_id="my-company")
print(sub_tenants.sub_tenant_ids)
```

### Check infrastructure status

```python
status = client.tenant.get_infra_status(tenant_id="my-company")
print(status.infra)
print(status.message)
```

### Monitor tenant stats

```python
stats = client.tenant.monitor(tenant_id="my-company")
print(stats)
```

### Delete a tenant

This permanently deletes the tenant and its data.

```python
client.tenant.delete_tenant(tenant_id="my-company")
```

---

## Upload knowledge files

Use `client.upload.knowledge(...)` to upload files for indexing.

### Single file upload

```python
from hydra_db import HydraDB

client = HydraDB(token="YOUR_API_KEY")

TENANT_ID = "my-company"
SUB_TENANT_ID = "my-sub-tenant"

with open("report.pdf", "rb") as f:
    upload = client.upload.knowledge(
        tenant_id=TENANT_ID,
        sub_tenant_id=SUB_TENANT_ID,
        files=[("report.pdf", f, "application/pdf")],
        upsert=True,
    )

print(upload.success)
print(upload.message)
print(upload.results[0].source_id)
print(upload.results[0].status)
```

The first upload response usually returns an initial status such as `queued`. That means the file was accepted and added to the ingestion queue. It does not mean ingestion is finished.

### Multiple file upload

```python
with open("a.pdf", "rb") as f1, open("b.pdf", "rb") as f2:
    upload = client.upload.knowledge(
        tenant_id=TENANT_ID,
        sub_tenant_id=SUB_TENANT_ID,
        files=[
            ("a.pdf", f1, "application/pdf"),
            ("b.pdf", f2, "application/pdf"),
        ],
        upsert=True,
    )

for item in upload.results or []:
    print(item.filename, item.source_id, item.status, item.error)
```

### Upload files with metadata

`file_metadata` must be a JSON string. The array length should match the number of uploaded files.

Each metadata object can include:

- `file_id`: optional custom source ID
- `metadata`: tenant-level metadata
- `additional_metadata`: document/source-level metadata
- `relations`: forceful relations to other source IDs

```python
import json

file_metadata = json.dumps([
    {
        "file_id": "doc_a",
        "metadata": {"department": "sales", "region": "us"},
        "additional_metadata": {"author": "Alice", "title": "Sales Report"},
    },
    {
        "file_id": "doc_b",
        "metadata": {"department": "marketing", "region": "us"},
        "additional_metadata": {"author": "Bob", "title": "Marketing Report"},
        "relations": {
            "cortex_source_ids": ["doc_a"],
            "properties": {"relation": "same_upload_batch"},
        },
    },
])

with open("a.pdf", "rb") as f1, open("b.pdf", "rb") as f2:
    upload = client.upload.knowledge(
        tenant_id=TENANT_ID,
        sub_tenant_id=SUB_TENANT_ID,
        files=[
            ("a.pdf", f1, "application/pdf"),
            ("b.pdf", f2, "application/pdf"),
        ],
        file_metadata=file_metadata,
        upsert=True,
    )

print(upload.results)
```

### Upload app-generated knowledge without files

`app_knowledge` accepts a JSON string containing one source object or an array of source objects.

```python
import json

app_knowledge = json.dumps([
    {
        "id": "app-source-001",
        "tenant_id": TENANT_ID,
        "sub_tenant_id": SUB_TENANT_ID,
        "title": "Internal onboarding note",
        "type": "document",
        "description": "Short internal note for onboarding",
        "content": {
            "text": "New users should be added to the onboarding workspace first."
        },
        "tenant_metadata": {"department": "engineering"},
        "document_metadata": {"source": "internal_app"},
    }
])

upload = client.upload.knowledge(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    app_knowledge=app_knowledge,
    upsert=True,
)

print(upload.results)
```

`app_sources` is still available as a deprecated alias, but new code should use `app_knowledge`.

---

## Verify ingestion status

Use `client.upload.verify_processing(...)` with the same `tenant_id`, `sub_tenant_id`, and returned `source_id` values from upload.

```python
source_ids = [item.source_id for item in upload.results or []]

status = client.upload.verify_processing(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    file_ids=source_ids,
)

for item in status.statuses:
    print(item.file_id, item.indexing_status, item.success, item.message)
```

Current indexing statuses include:

| Status | Meaning |
|---|---|
| `queued` | Upload was accepted and is waiting for processing. |
| `processing` | File parsing, chunking, embedding, or indexing is running. |
| `graph_creation` | Vector indexing is done or in progress, and knowledge graph creation is running. |
| `completed` | Ingestion completed successfully. |
| `success` | Alias for completed. |
| `errored` | Ingestion failed. Check `error_code` and `error_message`. |

### Poll until ingestion finishes

```python
import time

source_ids = [item.source_id for item in upload.results or []]

while True:
    batch = client.upload.verify_processing(
        tenant_id=TENANT_ID,
        sub_tenant_id=SUB_TENANT_ID,
        file_ids=source_ids,
    )

    statuses = [item.indexing_status for item in batch.statuses]
    print(statuses)

    if all(status in ("completed", "success") for status in statuses):
        print("Ingestion finished")
        break

    if any(status == "errored" for status in statuses):
        for item in batch.statuses:
            if item.indexing_status == "errored":
                print(item.file_id, item.error_code, item.error_message)
        break

    time.sleep(5)
```

You can pass a single string or a list to `file_ids`:

```python
status = client.upload.verify_processing(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    file_ids="doc_a",
)
```

`file_id` is also available for backward compatibility, but new code should use `file_ids`.

---

## Add memories

Use `client.upload.add_memory(...)` for free-form text, markdown, or conversation memory ingestion.

### Add plain text memory

```python
memory = client.upload.add_memory(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    upsert=True,
    memories=[
        {
            "source_id": "memory-001",
            "title": "User preference",
            "text": "User prefers detailed explanations and dark mode.",
            "infer": True,
            "user_name": "John",
            "metadata": {"category": "preference"},
            "additional_metadata": {"source": "chat"},
        }
    ],
)

print(memory.results)
```

### Add markdown memory

```python
memory = client.upload.add_memory(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    upsert=True,
    memories=[
        {
            "source_id": "meeting-notes-001",
            "title": "Meeting Notes",
            "text": "# Meeting Notes\n\n- Budget approved\n- Launch target: Q2",
            "is_markdown": True,
            "infer": False,
        }
    ],
)
```

### Add user-assistant conversation pairs

```python
memory = client.upload.add_memory(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    upsert=True,
    memories=[
        {
            "source_id": "conversation-001",
            "user_assistant_pairs": [
                {
                    "user": "What kind of reports do I like?",
                    "assistant": "You prefer weekly summary reports with charts.",
                },
                {
                    "user": "Which UI theme do I prefer?",
                    "assistant": "You prefer dark mode.",
                },
            ],
            "infer": True,
            "user_name": "John",
            "custom_instructions": "Extract durable user preferences.",
        }
    ],
)
```

### Delete a memory

```python
client.upload.delete_memory(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    memory_id="memory-001",
)
```

---

## Recall

### Full recall over knowledge sources

```python
results = client.recall.full_recall(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    query="What did the report say about revenue?",
    max_results=10,
    mode="fast",
    alpha=0.8,
    recency_bias=0.0,
    graph_context=True,
)

print(results.chunks)
print(results.sources)
```

`alpha` can be a float from `0.0` to `1.0`, or `"auto"`.

- `1.0`: more semantic/vector weighted
- `0.0`: more keyword/BM25 weighted
- `"auto"`: backend chooses the ranking balance

### Recall preferences over memories

```python
preferences = client.recall.recall_preferences(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    query="What UI theme does the user prefer?",
    max_results=5,
    mode="fast",
    alpha="auto",
)

print(preferences.chunks)
```

### Metadata filters in recall

Top-level keys match tenant metadata. To filter document metadata, use the `document_metadata` key.

```python
results = client.recall.full_recall(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    query="revenue forecast",
    max_results=10,
    metadata_filters={
        "department": "sales",
        "document_metadata": {"author": "Alice"},
    },
)
```

### Boolean recall

Use this for exact keyword, AND, OR, or phrase-style BM25 search.

```python
results = client.recall.boolean_recall(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    query="dark mode",
    operator="phrase",
    search_mode="memories",
    max_results=10,
)
```

Supported operators:

```text
or | and | phrase
```

Supported search modes:

```text
sources | memories
```

---

## Unified context retrieval

`recall_context` is a high-level helper that calls both `full_recall` (knowledge) and `recall_preferences` (memory) in a single call and merges the results. Use this when building agent context windows — it eliminates the need to call both endpoints and manually merge.

### Basic usage

```python
from hydra_db import HydraDB, build_string

client = HydraDB(token="YOUR_API_KEY")

result = client.recall_context(
    query="What does the user prefer?",
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
)

# Inject into an LLM prompt
prompt = build_string(result)
print(prompt)
```

### With graph context

```python
result = client.recall_context(
    query="What does the user prefer?",
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    graph_context=True,
)

prompt = build_string(result)
```

### Only knowledge or only memory

```python
# Knowledge only
result = client.recall_context(
    query="What did the report say about revenue?",
    tenant_id=TENANT_ID,
    include_memory=False,
    include_knowledge=True,
)

# Memory only
result = client.recall_context(
    query="What are the user preferences?",
    tenant_id=TENANT_ID,
    include_memory=True,
    include_knowledge=False,
)
```

### Result structure

```python
result.chunks                  # deduplicated chunks from both paths
result.sources                 # deduplicated sources from both paths
result.knowledge_graph_context # graph context from full_recall
result.memory_graph_context    # graph context from recall_preferences
result.raw_knowledge           # raw RetrievalResult from full_recall
result.raw_memory              # raw RetrievalResult from recall_preferences
```

### `build_string` output format

`build_string(result)` formats the merged result into a plain string ready for LLM injection. Each chunk is immediately followed by its graph relations:

```text
=== KNOWLEDGE CONTEXT ===

Chunk 1
Source: f098... (score: 1.33)

The zero-intervention credential flow relies on AWS credentials...

Graph Relations:
  [makefile] → RELATED_TO → [make bootstrap-staging]: The makefile defines...
  [github] → RELATED_TO → [aws credentials]: The credential flow relies on...
---

Chunk 2
Source: f098... (score: 1.31)

JWT Token — for 12 dashboard endpoints...
---

=== MEMORY CONTEXT ===

Chunk 1
Source: mem-xyz (score: 1.27)

User: Hi
Assistant: hello

Graph Relations:
  [user] → RELATED_TO → [vishal]: The user identifies themselves as Vishal.
---
```

### Use `build_string` with individual recall endpoints too

`build_string` also accepts a raw `RetrievalResult` from `full_recall` or `recall_preferences` directly:

```python
from hydra_db import HydraDB, build_string

result = client.recall.full_recall(tenant_id=TENANT_ID, query="What did the report say?")
print(build_string(result))  # renders as === CONTEXT ===

result = client.recall.recall_preferences(tenant_id=TENANT_ID, query="user preferences")
print(build_string(result))  # renders as === CONTEXT ===
```

### Standalone merge utility

If you already have results from both endpoints, use `merge_recall_results` directly:

```python
from hydra_db.helpers import merge_recall_results

memory = client.recall.recall_preferences(tenant_id=TENANT_ID, query="preferences")
knowledge = client.recall.full_recall(tenant_id=TENANT_ID, query="knowledge")

result = merge_recall_results(memory, knowledge)
prompt = build_string(result)
```

### Async usage

```python
import asyncio
from hydra_db import AsyncHydraDB, build_string

client = AsyncHydraDB(token="YOUR_API_KEY")

async def main():
    result = await client.recall_context(
        query="What does the user prefer?",
        tenant_id=TENANT_ID,
        graph_context=True,
    )
    print(build_string(result))

asyncio.run(main())
```

### All parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `query` | `str` | required | Search query sent to both endpoints |
| `tenant_id` | `str` | required | Tenant identifier |
| `sub_tenant_id` | `str` | `None` | Optional sub-tenant identifier |
| `include_memory` | `bool` | `True` | Whether to call `recall_preferences` |
| `include_knowledge` | `bool` | `True` | Whether to call `full_recall` |
| `max_results` | `int` | `None` | Maximum results per path |
| `mode` | `str` | `None` | `"fast"` or `"thinking"` |
| `alpha` | `str \| float` | `None` | Hybrid search balance (`0.0`–`1.0` or `"auto"`) |
| `recency_bias` | `float` | `None` | Preference for newer content (`0.0`–`1.0`) |
| `graph_context` | `bool` | `None` | Enable graph context |
| `search_forceful_relations` | `bool` | `None` | Search forceful relations in thinking mode |
| `additional_context` | `str` | `None` | Extra context to guide retrieval |
| `metadata_filters` | `dict` | `None` | Key-value metadata filters |

---

## Fetch and inspect indexed data

### List knowledge sources

```python
sources = client.fetch.list_data(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    kind="knowledge",
    page=1,
    page_size=50,
)

print(sources)
```

### List memories

```python
memories = client.fetch.list_data(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    kind="memories",
    page=1,
    page_size=50,
)
```

### Fetch specific source IDs

```python
sources = client.fetch.list_data(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    kind="knowledge",
    source_ids=["doc_a", "doc_b"],
)
```

### Filter list results

```python
filtered = client.fetch.list_data(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    kind="knowledge",
    filters={
        "tenant_metadata": {"department": "sales"},
        "document_metadata": {"author": "Alice"},
        "source_fields": {"type": "document"},
    },
)
```

### Include only selected fields

`include_fields` can reduce response size for knowledge list calls.

```python
sources = client.fetch.list_data(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    kind="knowledge",
    include_fields=["title", "document_metadata", "tenant_metadata"],
)
```

Allowed field names include:

```text
attachments, content, description, document_metadata, note, relations,
tenant_metadata, timestamp, title, type, url
```

### Fetch source content

```python
source = client.fetch.content(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    source_id="doc_a",
    mode="content",
)

print(source)
```

Supported modes:

```text
content | url | both
```

For presigned URLs, you can set expiry in seconds:

```python
source = client.fetch.content(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    source_id="doc_a",
    mode="url",
    expiry_seconds=3600,
)
```

### Fetch graph relations

```python
relations = client.fetch.graph_relations_by_source_id(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    source_id="doc_a",
    is_memory=False,
    limit=10,
)

print(relations)
```

To fetch relations across the whole sub-tenant, omit `source_id`:

```python
relations = client.fetch.graph_relations_by_source_id(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    limit=10,
)
```

---

## Delete data

Delete one or more source IDs from a tenant/sub-tenant.

```python
client.data.delete(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    ids=["doc_a", "doc_b"],
)
```

---

## Raw embeddings

Raw embedding APIs require a tenant created with `is_embeddings_tenant=True` and the correct `embeddings_dimension`.

### Insert embeddings

```python
client.embeddings.insert(
    tenant_id="my-embeddings-tenant",
    sub_tenant_id=SUB_TENANT_ID,
    upsert=True,
    embeddings=[
        {
            "source_id": "raw-doc-001",
            "metadata": {"category": "finance"},
            "embeddings": [
                {
                    "chunk_id": "raw-doc-001-chunk-0",
                    "embedding": [0.1, 0.2, 0.3],
                    "metadata": {"page": 1},
                },
                {
                    "chunk_id": "raw-doc-001-chunk-1",
                    "embedding": [0.4, 0.5, 0.6],
                    "metadata": {"page": 2},
                },
            ],
        }
    ],
)
```

Use vectors with the same dimension as the tenant's `embeddings_dimension`.

### Search by vector

```python
results = client.embeddings.search(
    tenant_id="my-embeddings-tenant",
    sub_tenant_id=SUB_TENANT_ID,
    query_embedding=[0.1, 0.2, 0.3],
    limit=10,
    output_fields=["source_id", "chunk_id", "metadata"],
)

print(results)
```

### Filter embeddings

```python
by_source = client.embeddings.filter(
    tenant_id="my-embeddings-tenant",
    sub_tenant_id=SUB_TENANT_ID,
    source_id="raw-doc-001",
    limit=50,
)

by_chunks = client.embeddings.filter(
    tenant_id="my-embeddings-tenant",
    sub_tenant_id=SUB_TENANT_ID,
    chunk_ids=["raw-doc-001-chunk-0", "raw-doc-001-chunk-1"],
)
```

### Delete embeddings

```python
client.embeddings.delete(
    tenant_id="my-embeddings-tenant",
    sub_tenant_id=SUB_TENANT_ID,
    source_id="raw-doc-001",
)

client.embeddings.delete(
    tenant_id="my-embeddings-tenant",
    sub_tenant_id=SUB_TENANT_ID,
    chunk_ids=["raw-doc-001-chunk-0"],
)
```

---

## Graph health

Fetch high-degree graph nodes for a tenant/sub-tenant.

```python
nodes = client.graph_health.get_super_nodes(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    degree_threshold=10,
    limit=20,
)

print(nodes)
```

---

## Ingestion pipeline helper

The SDK also exposes `client.ingestion_pipeline.ingest_memory(...)` for direct text ingestion through the ingestion pipeline.

```python
result = client.ingestion_pipeline.ingest_memory(
    tenant_id=TENANT_ID,
    sub_tenant_id=SUB_TENANT_ID,
    text="User prefers concise technical summaries.",
    infer=True,
    custom_instructions="Extract durable user preferences.",
    upsert=True,
    metadata={"category": "preference"},
    additional_metadata={"source": "app"},
)

print(result)
```

---

## API key management

This endpoint is intended for dashboard/session-token flows, not normal ingestion API keys.

```python
new_key = client.key.create_api_key(
    owner="service-account@example.com",
    scopes=["query"],
    env="live",
    prefix="sk",
)

print(new_key.full_api_key)
```

---

## Async usage

Every SDK group also has async methods on `AsyncHydraDB`.

```python
import asyncio
import os
from hydra_db import AsyncHydraDB

TENANT_ID = "my-company"
SUB_TENANT_ID = "my-sub-tenant"

async def main():
    client = AsyncHydraDB(token=os.environ["HYDRA_DB_API_KEY"])

    results = await client.recall.full_recall(
        tenant_id=TENANT_ID,
        sub_tenant_id=SUB_TENANT_ID,
        query="Which mode does the user prefer?",
        max_results=10,
        alpha="auto",
    )

    print(results.chunks)

asyncio.run(main())
```

Async file upload:

```python
import asyncio
import os
from hydra_db import AsyncHydraDB

TENANT_ID = "my-company"
SUB_TENANT_ID = "my-sub-tenant"

async def main():
    client = AsyncHydraDB(token=os.environ["HYDRA_DB_API_KEY"])

    with open("report.pdf", "rb") as f:
        upload = await client.upload.knowledge(
            tenant_id=TENANT_ID,
            sub_tenant_id=SUB_TENANT_ID,
            files=[("report.pdf", f, "application/pdf")],
            upsert=True,
        )

    print(upload.results)

asyncio.run(main())
```

---

## Error handling

The SDK raises typed errors for common API failures.

```python
from hydra_db import HydraDB
from hydra_db.errors import BadRequestError, UnauthorizedError, UnprocessableEntityError

client = HydraDB(token="YOUR_API_KEY")

try:
    upload = client.upload.knowledge(
        tenant_id=TENANT_ID,
        sub_tenant_id=SUB_TENANT_ID,
        files=[],
    )
except UnauthorizedError:
    print("Invalid or missing API key")
except BadRequestError as error:
    print("Bad request", error.body)
except UnprocessableEntityError as error:
    print("Validation error", error.body)
```

Common HTTP errors exposed by the SDK include:

```text
BadRequestError
UnauthorizedError
ForbiddenError
NotFoundError
UnprocessableEntityError
InternalServerError
ServiceUnavailableError
TooManyRequestsError
```

---

## SDK method reference

| Group | Method | Description |
|---|---|---|
| Client | `client.recall_context` | Unified helper — calls both recall paths and merges results. |
| Tenant | `client.tenant.create` | Create a standard tenant or raw embeddings tenant. |
| Tenant | `client.tenant.get_tenant_ids` | List tenant IDs. |
| Tenant | `client.tenant.get_sub_tenant_ids` | List sub-tenant IDs for a tenant. |
| Tenant | `client.tenant.get_infra_status` | Check tenant infrastructure status. |
| Tenant | `client.tenant.monitor` | Get tenant stats. |
| Tenant | `client.tenant.delete_tenant` | Delete a tenant. |
| Upload | `client.upload.knowledge` | Upload files or app-generated knowledge. |
| Upload | `client.upload.verify_processing` | Check ingestion status for uploaded source IDs. |
| Upload | `client.upload.add_memory` | Add text, markdown, or conversation memories. |
| Upload | `client.upload.delete_memory` | Delete a single memory by ID. |
| Recall | `client.recall.full_recall` | Search knowledge sources. |
| Recall | `client.recall.recall_preferences` | Search memories/preferences. |
| Recall | `client.recall.boolean_recall` | Keyword/BM25 search. |
| Fetch | `client.fetch.list_data` | List knowledge or memories. |
| Fetch | `client.fetch.content` | Fetch source content or URL. |
| Fetch | `client.fetch.graph_relations_by_source_id` | Fetch source graph relations. |
| Data | `client.data.delete` | Delete one or more source IDs. |
| Embeddings | `client.embeddings.insert` | Insert raw embedding vectors. |
| Embeddings | `client.embeddings.search` | Search raw embedding vectors. |
| Embeddings | `client.embeddings.filter` | Filter raw embeddings by source/chunk IDs. |
| Embeddings | `client.embeddings.delete` | Delete raw embeddings. |
| Graph health | `client.graph_health.get_super_nodes` | Fetch graph super nodes. |
| Pipeline | `client.ingestion_pipeline.ingest_memory` | Direct ingestion pipeline memory helper. |
| Key | `client.key.create_api_key` | Create an API key through dashboard/session-token flow. |

---

## Notes for contributors

This SDK is generated from the HydraDB API definition. If method signatures change in the generated code, update this README to match the generated clients under `src/hydra_db/*/client.py`.

Before publishing, verify these stay consistent:

- Package name in `pyproject.toml`
- Installation command in this README
- Import name `hydra_db`
- Upload metadata shape for `file_metadata`


---

## Links

- **Homepage:** [hydradb.com](https://www.hydradb.com/)
- **Documentation:** [docs.hydradb.com](https://docs.hydradb.com/)
- **API Reference:** [docs.hydradb.com/api-reference/introduction](https://docs.hydradb.com/api-reference/introduction)

## Support

If you have any questions or need help, reach out at [founders@hydradb.com](mailto:founders@usecortex.ai).
