import json
with open("/path/to/policy.pdf", "rb") as policy:
knowledge_result = client.context.ingest(
# Knowledge requests can include documents, app_knowledge, or both.
type="knowledge",
database="acme_corp",
collection="team_docs",
upsert=True,
# Use documents when HydraDB should parse PDFs, DOCX, CSV, Markdown, or text files.
documents=[("policy.pdf", policy, "application/pdf")],
document_metadata=json.dumps([
{
"id": "policy_main",
"metadata": {"department": "legal"},
"additional_metadata": {"source": "policy"},
}
]),
# Use app_knowledge when your app already extracted the source text/metadata.
app_knowledge=json.dumps([
{
"id": "slack_thread_001",
"database": "acme_corp",
"collection": "team_docs",
"title": "Pricing discussion",
"type": "slack",
"content": {"text": "We agreed on three tiers..."},
"metadata": {"department": "product"},
"additional_metadata": {"channel": "pricing"},
}
]),
)
text_memory_result = client.context.ingest(
type="memory",
database="acme_corp",
collection="user_alex",
memories=json.dumps([
{
# Use text for raw notes or signals.
"text": "Prefers concise answers and dark mode.",
"infer": True,
"user_name": "Alex",
}
]),
)
conversation_memory_result = client.context.ingest(
type="memory",
database="acme_corp",
collection="user_alex",
memories=json.dumps([
{
# Use user_assistant_pairs for conversation history instead of text.
"title": "Support conversation about refunds",
"user_assistant_pairs": [
{"user": "Can I get a refund?", "assistant": "Refunds are available within 30 days."},
],
"infer": False,
}
]),
)
const knowledgeResult = await client.context.ingest({
// Knowledge requests can include documents, app_knowledge, or both.
type: "knowledge",
database: "acme_corp",
collection: "team_docs",
upsert: true,
// Use documents when HydraDB should parse PDFs, DOCX, CSV, Markdown, or text files.
documents: [
{ path: "/path/to/policy.pdf", filename: "policy.pdf", contentType: "application/pdf" },
],
documentMetadata: JSON.stringify([
{
id: "policy_main",
metadata: { department: "legal" },
additional_metadata: { source: "policy" },
},
]),
// Use app_knowledge when your app already extracted the source text/metadata.
appKnowledge: JSON.stringify([
{
id: "slack_thread_001",
database: "acme_corp",
collection: "team_docs",
title: "Pricing discussion",
type: "slack",
content: { text: "We agreed on three tiers..." },
metadata: { department: "product" },
additional_metadata: { channel: "pricing" },
},
]),
});
const textMemoryResult = await client.context.ingest({
type: "memory",
database: "acme_corp",
collection: "user_alex",
memories: JSON.stringify([
{
// Use text for raw notes or signals.
text: "Prefers concise answers and dark mode.",
infer: true,
user_name: "Alex",
},
]),
});
const conversationMemoryResult = await client.context.ingest({
type: "memory",
database: "acme_corp",
collection: "user_alex",
memories: JSON.stringify([
{
// Use user_assistant_pairs for conversation history instead of text.
title: "Support conversation about refunds",
user_assistant_pairs: [
{ user: "Can I get a refund?", assistant: "Refunds are available within 30 days." },
],
infer: false,
},
]),
});
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=knowledge" \
-F "database=acme_corp" \
-F "collection=team_docs" \
-F "upsert=true" \
-F "documents=@/path/to/policy.pdf" \
-F 'document_metadata=[
{
"id": "policy_main",
"metadata": { "department": "legal" },
"additional_metadata": { "source": "policy" }
}
]' \
-F 'app_knowledge=[
{
"id": "slack_thread_001",
"database": "acme_corp",
"collection": "team_docs",
"title": "Pricing discussion",
"type": "slack",
"content": { "text": "We agreed on three tiers..." },
"metadata": { "department": "product" },
"additional_metadata": { "channel": "pricing" }
}
]'
# OR: memory from raw text.
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=memory" \
-F "database=acme_corp" \
-F "collection=user_alex" \
-F 'memories=[
{
"text": "Prefers concise answers and dark mode.",
"infer": true,
"user_name": "Alex"
}
]'
# OR: memory from conversation pairs.
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=memory" \
-F "database=acme_corp" \
-F "collection=user_alex" \
-F 'memories=[
{
"title": "Support conversation about refunds",
"user_assistant_pairs": [
{ "user": "Can I get a refund?", "assistant": "Refunds are available within 30 days." }
],
"infer": false
}
]'
{
"data": {
"failed_count": 0,
"message": "Success",
"results": [
{
"error": "",
"filename": "policy.pdf",
"id": "HydraDoc1234",
"infer": true,
"relations_created": 5,
"status": "queued",
"title": "Project Phoenix Overview"
}
],
"success": true,
"success_count": 2
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"collection": "team_docs",
"database": "acme_corp",
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
"source_type": "file",
"sub_tenant_id": "sub_tenant_4567",
"tenant_id": "tenant_1234"
},
"success": true
}{
"data": "<unknown>",
"detail": {
"deprecated": true,
"deprecated_field": "tenant_id",
"error_code": "VALIDATION_ERROR",
"message": "Request validation failed",
"preferred_field": "database"
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d"
},
"success": true
}{
"data": "<unknown>",
"detail": {
"deprecated": true,
"deprecated_field": "tenant_id",
"error_code": "VALIDATION_ERROR",
"message": "Request validation failed",
"preferred_field": "database"
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d"
},
"success": true
}{
"data": "<unknown>",
"detail": {
"deprecated": true,
"deprecated_field": "tenant_id",
"error_code": "VALIDATION_ERROR",
"message": "Request validation failed",
"preferred_field": "database"
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d"
},
"success": true
}{
"data": "<unknown>",
"detail": {
"deprecated": true,
"deprecated_field": "tenant_id",
"error_code": "VALIDATION_ERROR",
"message": "Request validation failed",
"preferred_field": "database"
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d"
},
"success": true
}Ingest Context
Ingestion endpoint for knowledge (documents, app sources) and user memories.
import json
with open("/path/to/policy.pdf", "rb") as policy:
knowledge_result = client.context.ingest(
# Knowledge requests can include documents, app_knowledge, or both.
type="knowledge",
database="acme_corp",
collection="team_docs",
upsert=True,
# Use documents when HydraDB should parse PDFs, DOCX, CSV, Markdown, or text files.
documents=[("policy.pdf", policy, "application/pdf")],
document_metadata=json.dumps([
{
"id": "policy_main",
"metadata": {"department": "legal"},
"additional_metadata": {"source": "policy"},
}
]),
# Use app_knowledge when your app already extracted the source text/metadata.
app_knowledge=json.dumps([
{
"id": "slack_thread_001",
"database": "acme_corp",
"collection": "team_docs",
"title": "Pricing discussion",
"type": "slack",
"content": {"text": "We agreed on three tiers..."},
"metadata": {"department": "product"},
"additional_metadata": {"channel": "pricing"},
}
]),
)
text_memory_result = client.context.ingest(
type="memory",
database="acme_corp",
collection="user_alex",
memories=json.dumps([
{
# Use text for raw notes or signals.
"text": "Prefers concise answers and dark mode.",
"infer": True,
"user_name": "Alex",
}
]),
)
conversation_memory_result = client.context.ingest(
type="memory",
database="acme_corp",
collection="user_alex",
memories=json.dumps([
{
# Use user_assistant_pairs for conversation history instead of text.
"title": "Support conversation about refunds",
"user_assistant_pairs": [
{"user": "Can I get a refund?", "assistant": "Refunds are available within 30 days."},
],
"infer": False,
}
]),
)
const knowledgeResult = await client.context.ingest({
// Knowledge requests can include documents, app_knowledge, or both.
type: "knowledge",
database: "acme_corp",
collection: "team_docs",
upsert: true,
// Use documents when HydraDB should parse PDFs, DOCX, CSV, Markdown, or text files.
documents: [
{ path: "/path/to/policy.pdf", filename: "policy.pdf", contentType: "application/pdf" },
],
documentMetadata: JSON.stringify([
{
id: "policy_main",
metadata: { department: "legal" },
additional_metadata: { source: "policy" },
},
]),
// Use app_knowledge when your app already extracted the source text/metadata.
appKnowledge: JSON.stringify([
{
id: "slack_thread_001",
database: "acme_corp",
collection: "team_docs",
title: "Pricing discussion",
type: "slack",
content: { text: "We agreed on three tiers..." },
metadata: { department: "product" },
additional_metadata: { channel: "pricing" },
},
]),
});
const textMemoryResult = await client.context.ingest({
type: "memory",
database: "acme_corp",
collection: "user_alex",
memories: JSON.stringify([
{
// Use text for raw notes or signals.
text: "Prefers concise answers and dark mode.",
infer: true,
user_name: "Alex",
},
]),
});
const conversationMemoryResult = await client.context.ingest({
type: "memory",
database: "acme_corp",
collection: "user_alex",
memories: JSON.stringify([
{
// Use user_assistant_pairs for conversation history instead of text.
title: "Support conversation about refunds",
user_assistant_pairs: [
{ user: "Can I get a refund?", assistant: "Refunds are available within 30 days." },
],
infer: false,
},
]),
});
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=knowledge" \
-F "database=acme_corp" \
-F "collection=team_docs" \
-F "upsert=true" \
-F "documents=@/path/to/policy.pdf" \
-F 'document_metadata=[
{
"id": "policy_main",
"metadata": { "department": "legal" },
"additional_metadata": { "source": "policy" }
}
]' \
-F 'app_knowledge=[
{
"id": "slack_thread_001",
"database": "acme_corp",
"collection": "team_docs",
"title": "Pricing discussion",
"type": "slack",
"content": { "text": "We agreed on three tiers..." },
"metadata": { "department": "product" },
"additional_metadata": { "channel": "pricing" }
}
]'
# OR: memory from raw text.
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=memory" \
-F "database=acme_corp" \
-F "collection=user_alex" \
-F 'memories=[
{
"text": "Prefers concise answers and dark mode.",
"infer": true,
"user_name": "Alex"
}
]'
# OR: memory from conversation pairs.
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=memory" \
-F "database=acme_corp" \
-F "collection=user_alex" \
-F 'memories=[
{
"title": "Support conversation about refunds",
"user_assistant_pairs": [
{ "user": "Can I get a refund?", "assistant": "Refunds are available within 30 days." }
],
"infer": false
}
]'
{
"data": {
"failed_count": 0,
"message": "Success",
"results": [
{
"error": "",
"filename": "policy.pdf",
"id": "HydraDoc1234",
"infer": true,
"relations_created": 5,
"status": "queued",
"title": "Project Phoenix Overview"
}
],
"success": true,
"success_count": 2
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"collection": "team_docs",
"database": "acme_corp",
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
"source_type": "file",
"sub_tenant_id": "sub_tenant_4567",
"tenant_id": "tenant_1234"
},
"success": true
}{
"data": "<unknown>",
"detail": {
"deprecated": true,
"deprecated_field": "tenant_id",
"error_code": "VALIDATION_ERROR",
"message": "Request validation failed",
"preferred_field": "database"
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d"
},
"success": true
}{
"data": "<unknown>",
"detail": {
"deprecated": true,
"deprecated_field": "tenant_id",
"error_code": "VALIDATION_ERROR",
"message": "Request validation failed",
"preferred_field": "database"
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d"
},
"success": true
}{
"data": "<unknown>",
"detail": {
"deprecated": true,
"deprecated_field": "tenant_id",
"error_code": "VALIDATION_ERROR",
"message": "Request validation failed",
"preferred_field": "database"
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d"
},
"success": true
}{
"data": "<unknown>",
"detail": {
"deprecated": true,
"deprecated_field": "tenant_id",
"error_code": "VALIDATION_ERROR",
"message": "Request validation failed",
"preferred_field": "database"
},
"error": {
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
},
"meta": {
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d"
},
"success": true
}type=knowledge:
- Documents - Use the
documentsfield for binary uploads that HydraDB should parse: PDFs, Office and iWork files, spreadsheets, images and plain text. See Supported file formats for the full list. - App Sources - Use
app_knowledgefor pre-extracted JSON content (Slack threads, Notion pages, emails, tickets). Read more about ingesting knowledge from your apps.
type=memory
Use memories for per-user content, scoped with collection. Set infer: true to let HydraDB extract preferences from raw signals, or infer: false to store the text verbatim. Read more about ingesting memories.
database and collection are the current field names (formerly tenant_id and sub_tenant_id). The old names remain accepted as deprecated aliases for full backward compatibility.import json
with open("/path/to/policy.pdf", "rb") as policy:
knowledge_result = client.context.ingest(
# Knowledge requests can include documents, app_knowledge, or both.
type="knowledge",
database="acme_corp",
collection="team_docs",
upsert=True,
# Use documents when HydraDB should parse PDFs, DOCX, CSV, Markdown, or text files.
documents=[("policy.pdf", policy, "application/pdf")],
document_metadata=json.dumps([
{
"id": "policy_main",
"metadata": {"department": "legal"},
"additional_metadata": {"source": "policy"},
}
]),
# Use app_knowledge when your app already extracted the source text/metadata.
app_knowledge=json.dumps([
{
"id": "slack_thread_001",
"database": "acme_corp",
"collection": "team_docs",
"title": "Pricing discussion",
"type": "slack",
"content": {"text": "We agreed on three tiers..."},
"metadata": {"department": "product"},
"additional_metadata": {"channel": "pricing"},
}
]),
)
text_memory_result = client.context.ingest(
type="memory",
database="acme_corp",
collection="user_alex",
memories=json.dumps([
{
# Use text for raw notes or signals.
"text": "Prefers concise answers and dark mode.",
"infer": True,
"user_name": "Alex",
}
]),
)
conversation_memory_result = client.context.ingest(
type="memory",
database="acme_corp",
collection="user_alex",
memories=json.dumps([
{
# Use user_assistant_pairs for conversation history instead of text.
"title": "Support conversation about refunds",
"user_assistant_pairs": [
{"user": "Can I get a refund?", "assistant": "Refunds are available within 30 days."},
],
"infer": False,
}
]),
)
const knowledgeResult = await client.context.ingest({
// Knowledge requests can include documents, app_knowledge, or both.
type: "knowledge",
database: "acme_corp",
collection: "team_docs",
upsert: true,
// Use documents when HydraDB should parse PDFs, DOCX, CSV, Markdown, or text files.
documents: [
{ path: "/path/to/policy.pdf", filename: "policy.pdf", contentType: "application/pdf" },
],
documentMetadata: JSON.stringify([
{
id: "policy_main",
metadata: { department: "legal" },
additional_metadata: { source: "policy" },
},
]),
// Use app_knowledge when your app already extracted the source text/metadata.
appKnowledge: JSON.stringify([
{
id: "slack_thread_001",
database: "acme_corp",
collection: "team_docs",
title: "Pricing discussion",
type: "slack",
content: { text: "We agreed on three tiers..." },
metadata: { department: "product" },
additional_metadata: { channel: "pricing" },
},
]),
});
const textMemoryResult = await client.context.ingest({
type: "memory",
database: "acme_corp",
collection: "user_alex",
memories: JSON.stringify([
{
// Use text for raw notes or signals.
text: "Prefers concise answers and dark mode.",
infer: true,
user_name: "Alex",
},
]),
});
const conversationMemoryResult = await client.context.ingest({
type: "memory",
database: "acme_corp",
collection: "user_alex",
memories: JSON.stringify([
{
// Use user_assistant_pairs for conversation history instead of text.
title: "Support conversation about refunds",
user_assistant_pairs: [
{ user: "Can I get a refund?", assistant: "Refunds are available within 30 days." },
],
infer: false,
},
]),
});
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=knowledge" \
-F "database=acme_corp" \
-F "collection=team_docs" \
-F "upsert=true" \
-F "documents=@/path/to/policy.pdf" \
-F 'document_metadata=[
{
"id": "policy_main",
"metadata": { "department": "legal" },
"additional_metadata": { "source": "policy" }
}
]' \
-F 'app_knowledge=[
{
"id": "slack_thread_001",
"database": "acme_corp",
"collection": "team_docs",
"title": "Pricing discussion",
"type": "slack",
"content": { "text": "We agreed on three tiers..." },
"metadata": { "department": "product" },
"additional_metadata": { "channel": "pricing" }
}
]'
# OR: memory from raw text.
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=memory" \
-F "database=acme_corp" \
-F "collection=user_alex" \
-F 'memories=[
{
"text": "Prefers concise answers and dark mode.",
"infer": true,
"user_name": "Alex"
}
]'
# OR: memory from conversation pairs.
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=memory" \
-F "database=acme_corp" \
-F "collection=user_alex" \
-F 'memories=[
{
"title": "Support conversation about refunds",
"user_assistant_pairs": [
{ "user": "Can I get a refund?", "assistant": "Refunds are available within 30 days." }
],
"infer": false
}
]'
Upload in-memory text as a .txt file
If you already have text in memory, create a file-like object and upload it through documents as a text/plain .txt file.
import io
import json
text = """Q4 planning notes
- Launch checklist is owned by Priya.
- Legal review is due by Friday.
"""
# Create a file-like object in memory. No local .txt file is required.
txt_file = io.BytesIO(text.encode("utf-8"))
response = client.context.ingest(
type="knowledge",
database="acme_corp",
collection="team_docs",
documents=[("meeting-notes.txt", txt_file, "text/plain")],
document_metadata=json.dumps([
{"id": "meeting_notes_q4", "metadata": {"department": "product"}}
]),
)
const text = `Q4 planning notes
- Launch checklist is owned by Priya.
- Legal review is due by Friday.
`;
const response = await client.context.ingest({
type: "knowledge",
database: "acme_corp",
collection: "team_docs",
documents: [
{
filename: "meeting-notes.txt",
contentType: "text/plain",
data: Buffer.from(text, "utf-8"),
},
],
documentMetadata: JSON.stringify([
{ id: "meeting_notes_q4", metadata: { department: "product" } },
]),
});
import io
import json
import requests
text = """Q4 planning notes
- Launch checklist is owned by Priya.
- Legal review is due by Friday.
"""
# Create a file-like object in memory. No local .txt file is required.
txt_file = io.BytesIO(text.encode("utf-8"))
response = requests.post(
"https://api.hydradb.com/context/ingest",
headers={
"Authorization": "Bearer <your_api_key>",
"API-Version": "2",
},
data={
"type": "knowledge",
"database": "acme_corp",
"collection": "team_docs",
"document_metadata": json.dumps([
{"id": "meeting_notes_q4", "metadata": {"department": "product"}}
]),
},
files={
"documents": ("meeting-notes.txt", txt_file, "text/plain"),
},
)
response.raise_for_status()
Important form fields
| Name | Description |
|---|---|
Use singular "memory" when writing memories. (default="knowledge") | |
Target database. Formerly tenant_id; the tenant_id alias is still accepted (deprecated). | |
Logical partition inside the database. Formerly sub_tenant_id; the sub_tenant_id alias is still accepted (deprecated). (default="" - default collection) | |
Replace existing sources with the same ID. Set to false to error on conflict. (default=true) | |
Binary uploads, knowledge only. Required when type=knowledge and you want HydraDB to parse documents. Omit when ingesting memories. See Supported file formats. (default=[]) | |
One entry per file in documents, in the same order. If omitted, documents index with inferred defaults such as filename/title. See the item shape below. | |
Pre-extracted source objects (Slack, Notion, web pages, etc.), knowledge only. See the app_knowledge item shape below. | |
Map of source id → your own entities + relations - replaces LLM graph extraction for each keyed source. Works for type=knowledge (key = a document_metadata id or app_knowledge item id) and type=memory (key = a memory id). See Bring Your Own Graph and the shape below. | |
Memory items, memory only. Required and non-empty when type=memory. Use plural memories for the form field, even though type is singular memory. See the memories item shape below. |
-
idmust not contain a comma (,). The comma is reserved as the id separator on Ingestion Status (GET /context/status?ids=a,b), so anidcontaining a comma cannot be looked up unambiguously. This applies to everyidyou supply -document_metadata,app_knowledge, andmemoriesitems. Ingesting an item whoseidcontains a comma is rejected with a400. -
202 Acceptedmeans queued, not indexed. Ingestion is asynchronous. A successful response only confirms your sources were accepted, not that they are ready to query. Before querying, pollGET /context/statuswith the returned IDs until each source reachescompletedorerrored. Alternatively, register a webhook forindexing.status_changedevents (see Webhooks).
Supported file formats
Anything in this table can be uploaded throughdocuments and HydraDB will read it.
| Type | Formats |
|---|---|
| Documents | .pdf .doc .docx .docm .dot .dotx .odt .ott .rtf |
| Spreadsheets | .xls .xlsx .xlsm .xlsb .ods .ots .csv .tsv |
| Presentations | .ppt .pptx .pptm .odp .otp |
| Apple iWork | .pages .numbers .key |
| Images | .png .jpg .jpeg .tif .tiff .webp .gif .bmp |
| Plain text | .txt .md .markdown .json |
| Type | What gets indexed |
|---|---|
| Documents and presentations | The text, in reading order. Slide titles and body text both come through. |
| Spreadsheets | Cell values rendered as a table, one per sheet. Formulas are indexed as their computed result, not the formula itself. |
| Images and scans | Text read out of the picture with OCR. A photo of a page and a scan of a page both work. |
| Plain text | The file as-is. |
Formats we cannot read
These are rejected the moment you upload them, before anything is queued. You get the answer in the upload response rather than minutes later.| Format | Upload this instead |
|---|---|
.heic .heif .avif | Export the photo as JPEG or PDF |
.svg .ico | Export as PNG or JPEG |
.html .htm .xml | Save the page as PDF |
.eml .msg | Save the message as PDF, or send it as an app source |
.epub .wpd | Convert to PDF or DOCX |
.zip | Upload the files individually |
.heic is the format iPhones use for photos by default, so it is the one people hit most often without realizing. On iOS you can change this under Settings > Camera > Formats > Most Compatible, which makes the camera save JPEGs instead. Or export the photo as JPEG or PDF before uploading.How we decide the format
The file extension is what counts.report.pdf is treated as a PDF because of the .pdf, not because of what is inside it.
If the filename has no extension at all, the Content-Type you send with that part is used instead, so a file with no extension in its name, sent as application/pdf, is accepted.
.heic photo renamed to photo.pdf passes this check, because the check reads the name, then fails later during parsing and comes back as errored on GET /context/status. Upload files under their real extension.Size limit
Each file indocuments can be up to 50 MB. A larger file is rejected with 413 and a message naming the file, and no part of the request is processed. Split large documents, or upload them separately.
When a file is not supported
A rejected file does not stop the rest of your upload. Each file is checked on its own, so a batch of ten with one bad file still indexes the other nine. The request returns202 as usual. The rejected file comes back with status: "failed" and error_code: "E1002", and every other file is queued normally:
{
"success": true,
"data": {
"success": false,
"message": "Upload completed with some failures.",
"results": [
{ "id": "6cc7bcd185b044cd", "filename": "report.pdf", "status": "queued", "error": null, "error_code": null },
{ "id": "f47411c736447126", "filename": "photo.heic", "status": "failed", "error": "This file format isn't supported. Please upload a PDF, Office document (Word, Excel, PowerPoint), image, CSV or text file.", "error_code": "E1002" },
{ "id": "d0c919bdec196a8d", "filename": "notes.txt", "status": "queued", "error": null, "error_code": null }
],
"success_count": 2,
"failed_count": 1
},
"error": null,
"meta": { "request_id": "bcd03673-174d-4a73-83e6-73bfcdc16061", "api_version": "2.0.1" }
}
results, success_count and failed_count sit inside data, not at the top level, like every other v2 response. The outer success: true means the request was accepted; data.success is what tells you whether every file in it was queued.GET /context/status for a file rejected with E1002. The file never entered the pipeline, so it has no status record, and looking it up returns FILE_NOT_FOUND rather than the format error you were given. The upload response is the only place E1002 appears. Read error_code on each item in data.results and act on it there.Common use-cases and their configurations
Document metadata
Per-document metadata (id, metadata, additional_metadata, relations) can be passed alongside each uploaded document to control indexing, filtering, and display. The key list is closed - an item carrying any other key is rejected with a 400 naming it, rather than being silently dropped. See the field reference below.
Knowledge documents - upload PDFs, DOCX, CSV, markdown, or text
Knowledge documents - upload PDFs, DOCX, CSV, markdown, or text
import json
with open("/path/to/policy.pdf", "rb") as f1, open("/path/to/runbook.pdf", "rb") as f2:
result = client.context.ingest(
type="knowledge",
database="acme_corp",
documents=[
("policy.pdf", f1, "application/pdf"),
("runbook.pdf", f2, "application/pdf"),
],
document_metadata=json.dumps([
{"id": "policy_main", "metadata": {"department": "legal"}},
{"id": "runbook_deploy", "metadata": {"department": "ops"}},
]),
)
const result = await client.context.ingest({
type: "knowledge",
database: "acme_corp",
documents: [
{ path: "/path/to/policy.pdf", filename: "policy.pdf", contentType: "application/pdf" },
{ path: "/path/to/runbook.pdf", filename: "runbook.pdf", contentType: "application/pdf" },
],
documentMetadata: JSON.stringify([
{ id: "policy_main", metadata: { department: "legal" } },
{ id: "runbook_deploy", metadata: { department: "ops" } },
]),
});
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=knowledge" \
-F "database=acme_corp" \
-F "documents=@/path/to/policy.pdf" \
-F "documents=@/path/to/runbook.pdf" \
-F 'document_metadata=[
{ "id": "policy_main", "metadata": { "department": "legal" } },
{ "id": "runbook_deploy", "metadata": { "department": "ops" } }
]'
| Field | Description |
|---|---|
Optional context ID. If set, becomes the id for this document (use your app’s document ID for parity). Must not contain a comma (,) - it is reserved as the id separator on /context/status?ids=. | |
Database-schema fields for filtering and search. Keys must be declared in database_metadata_schema. (default={}) | |
Free-form per-document fields for display or bookkeeping. To filter on these at search time, nest under metadata_filters.additional_metadata. (default={}) | |
Declare forceful relations to other sources. Shape: { "ids": ["...", "..."] }. Surfaced via additional_context in mode: "thinking" search. |
title, type, url and timestamp - is rejected with a 400 naming the unsupported key, rather than being silently dropped.In particular, a document’s title is derived, not settable. It defaults to the uploaded filename and is returned as source_title on query results and title on /context/list. It cannot be overridden at ingest, and PATCH /context/{id}/metadata only merges additional_metadata and database_metadata. Use additional_metadata for your own display fields, or ingest through app_knowledge, whose items carry an explicit title.App sources - index text from your workspace or personal apps
App sources - index text from your workspace or personal apps
import json
client.context.ingest(
type="knowledge",
database="acme_corp",
collection="team_docs",
app_knowledge=json.dumps([
{
"id": "slack_thread_001",
"database": "acme_corp",
"collection": "team_docs",
"title": "Pricing discussion",
"type": "slack",
"content": {"text": "We agreed on three tiers..."},
"metadata": {"channel": "product"},
}
]),
)
await client.context.ingest({
type: "knowledge",
database: "acme_corp",
collection: "team_docs",
appKnowledge: JSON.stringify([
{
id: "slack_thread_001",
database: "acme_corp",
collection: "team_docs",
title: "Pricing discussion",
type: "slack",
content: { text: "We agreed on three tiers..." },
metadata: { channel: "product" },
},
]),
});
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=knowledge" \
-F "database=acme_corp" \
-F "collection=team_docs" \
-F 'app_knowledge=[
{
"id": "slack_thread_001",
"database": "acme_corp",
"collection": "team_docs",
"title": "Pricing discussion",
"type": "slack",
"content": { "text": "We agreed on three tiers..." },
"metadata": { "channel": "product" }
}
]'
| Field | Description |
|---|---|
Context ID. Treated as the upsert key. Send an empty string to have one generated upstream. Must not contain a comma (,) - it is reserved as the id separator on /context/status?ids=. | |
Target database. Must match the form-level database. Formerly tenant_id; the tenant_id alias is still accepted (deprecated). | |
Logical partition inside the database. Must match the form-level collection. Formerly sub_tenant_id; the sub_tenant_id alias is still accepted (deprecated). | |
| Short title or subject shown in search results. | |
Source category (slack, notion, gmail, webpage, etc.). Used for filtering and display. | |
| Optional long-form description. | |
| Canonical URL or reference link. | |
| ISO-8601 timestamp (creation or last-updated). | |
Content payload. Use { "text": "..." } for plain text. Required for app sources. | |
Database-schema fields. (default={}) | |
Free-form per-document fields. (default={}) | |
Optional related attachments. (default=[]) | |
Forceful relations, same shape as on metadata. |
3. User memories - store notes or infer user preferences
3. User memories - store notes or infer user preferences
import json
client.context.ingest(
type="memory",
database="acme_corp",
collection="user_alex",
memories=json.dumps([
{
"text": "Prefers dark mode and short answers.",
"infer": True,
"user_name": "Alex",
}
]),
)
await client.context.ingest({
type: "memory",
database: "acme_corp",
collection: "user_alex",
memories: JSON.stringify([
{
text: "Prefers dark mode and short answers.",
infer: true,
user_name: "Alex",
},
]),
});
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=memory" \
-F "database=acme_corp" \
-F "collection=user_alex" \
-F 'memories=[
{
"text": "Prefers dark mode and short answers.",
"infer": true,
"user_name": "Alex"
}
]'
| Field | Description |
|---|---|
Optional unique ID. Acts as the upsert key. Must not contain a comma (,) - it is reserved as the id separator on /context/status?ids=. (default=auto-generated) | |
Short label for display in /context/list and search hits as source.title. (default=truncated text) | |
Raw text or markdown content. Required unless user_assistant_pairs is provided. | |
Conversation pairs { user, assistant }. Required unless text is provided. | |
Treat text as markdown for chunking. (default=false) | |
When true, HydraDB extracts the underlying preference from raw signal. (default=false) | |
Guides extraction when infer: true. Ignored when infer: false. | |
The user’s name. Feeds inference. (default="User") | |
| TTL in seconds. Memory stops surfacing after expiry. | |
Database-schema fields as a JSON-stringified object (e.g. "{\"department\":\"legal\"}"). Unlike metadata and app_knowledge, memory items take this as a string, not an object. (default="") | |
Free-form per-document fields as a JSON-stringified object. Same string-vs-object difference as metadata above. (default="") | |
Forceful relations within the Memories store. Shape: { "ids": ["...", "..."] }. |
4. Chat/LLM conversation pairs - store chat history or support context
4. Chat/LLM conversation pairs - store chat history or support context
import json
client.context.ingest(
type="memory",
database="acme_corp",
collection="user_alex",
memories=json.dumps([
{
"title": "Support conversation about refunds",
"user_assistant_pairs": [
{"user": "Can I get a refund?", "assistant": "Refunds are available within 30 days."},
],
"infer": False,
}
]),
)
await client.context.ingest({
type: "memory",
database: "acme_corp",
collection: "user_alex",
memories: JSON.stringify([
{
title: "Support conversation about refunds",
user_assistant_pairs: [
{ user: "Can I get a refund?", assistant: "Refunds are available within 30 days." },
],
infer: false,
},
]),
});
curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=memory" \
-F "database=acme_corp" \
-F "collection=user_alex" \
-F 'memories=[
{
"title": "Support conversation about refunds",
"user_assistant_pairs": [
{ "user": "Can I get a refund?", "assistant": "Refunds are available within 30 days." }
],
"infer": false
}
]'
Bring your own graph - supply entities and relations
Bring your own graph - supply entities and relations
graph_payload is a map of source id → graph that replaces LLM graph extraction for each keyed source. For type=knowledge, the key is a document_metadata id or an app_knowledge item id; for type=memory, the key is a memory id. Keyed sources are still chunked and embedded, so they stay searchable. See Bring Your Own Graph for the full guide.curl -X POST 'https://api.hydradb.com/context/ingest' \
-H "Authorization: Bearer <your_api_key>" \
-H "API-Version: 2" \
-F "type=knowledge" \
-F "database=acme_corp" \
-F "documents=@/path/to/policy.pdf" \
-F 'document_metadata=[{ "id": "billing-policy-doc" }]' \
-F 'graph_payload={
"billing-policy-doc": {
"entities": {
"alice": { "name": "Alice Carter", "type": "PERSON", "namespace": "employees" },
"billing": { "name": "Billing Policy", "type": "POLICY", "namespace": "policies" }
},
"relations": [
{ "source": "alice", "target": "billing", "predicate": "OWNS",
"context": "Alice Carter owns the billing policy." }
]
}
}'
import json
with open("/path/to/policy.pdf", "rb") as f:
client.context.ingest(
type="knowledge",
database="acme_corp",
documents=[("policy.pdf", f, "application/pdf")],
document_metadata=json.dumps([{"id": "billing-policy-doc"}]),
graph_payload=json.dumps({
"billing-policy-doc": {
"entities": {
"alice": {"name": "Alice Carter", "type": "PERSON", "namespace": "employees"},
"billing": {"name": "Billing Policy", "type": "POLICY", "namespace": "policies"},
},
"relations": [
{"source": "alice", "target": "billing", "predicate": "OWNS",
"context": "Alice Carter owns the billing policy."},
],
}
}),
)
await client.context.ingest({
type: "knowledge",
database: "acme_corp",
documents: [{ path: "/path/to/policy.pdf", filename: "policy.pdf", contentType: "application/pdf" }],
documentMetadata: JSON.stringify([{ id: "billing-policy-doc" }]),
graphPayload: JSON.stringify({
"billing-policy-doc": {
entities: {
alice: { name: "Alice Carter", type: "PERSON", namespace: "employees" },
billing: { name: "Billing Policy", type: "POLICY", namespace: "policies" },
},
relations: [
{ source: "alice", target: "billing", predicate: "OWNS",
context: "Alice Carter owns the billing policy." },
],
},
}),
});
| Field | Description |
|---|---|
Top-level key: a document_metadata id or app_knowledge item id for type=knowledge, or a memory id for type=memory. Value is that source’s graph. A key matching no source returns 400. | |
Map keyed by a caller-local id; each value is an entity. The key is only a handle for relations to reference - it is not stored. | |
| Entity name. Normalized (lowercased) server-side so it matches at query time. ≤ 256 chars. | |
Entity type (e.g. PERSON, POLICY). Stored as supplied. | |
| Logical grouping for the entity. Stored as supplied. | |
| Optional external id (email, URL, etc.) - display only. | |
| Edges referencing entity-map keys. | |
| Entity-map key of the source entity. | |
| Entity-map key of the target entity. | |
| Relationship label, any plain string. ≤ 256 chars. | |
| Optional sentence supporting the edge. ≤ 2,000 chars. | |
| Optional timing info (e.g. “since 2021”, “in Q3”). |
document_metadata id or app_knowledge item id for type=knowledge, or a memory id for type=memory, in the same request; attach graphs to multiple sources at once. Extraction is skipped for keyed sources. Caps per graph: ≤ 5,000 entities, ≤ 10,000 relations, ≤ 500 relations per entity; over-cap returns 400. Graphs survive re-ingest (re-upload or connector re-sync re-applies the stored graph).Some important notes
- Async indexing.
202 Acceptedmeans HydraDB queued the work, not that content is searchable. Poll Ingestion Status untilindexing_statusreachesgraph_creation(searchable) orcompleted(graph-ready). - Multipart, not JSON. This endpoint uses
multipart/form-data. Stringify all JSON arrays (metadata,app_knowledge,memories) before placing them in the form field. - Declare hot schema fields upfront. Put frequently filtered fields in
metadata, define them indatabase_metadata_schemawithenable_match: true, and useadditional_metadatafor free-form display/bookkeeping fields. Define filterable fields when creating the database via Create Database. Additive schema updates exist, but newly added dense/sparse metadata lanes are not backfilled into existing Milvus collections. - Memory vs knowledge. Use
type: "memory"for memory ingestion, listing, and deletion. Usetype: "all"onPOST /querywhen results should combine both. The multipart field name for memories is alwaysmemories. - Collection defaulting. Omitting
collectionwrites to the default collection. List available collections with List Collections.
- Always check ingestion status to ensure context is ready to be retrieved
- Query once context is ready
- Inspect: List Documents helps you fetch titles and descriptions of ingested context
- Inspect: Fetch Content helps you fetch full context of a document, memory, knowledge item
- Cleanup: Delete Context
Authorizations
API key sent as a Bearer token: "Bearer prefix.secret"
Body
Content type: 'knowledge' or 'memory' | Database (canonical name for the tenant scope) | Collection (canonical name for the sub-tenant scope) | Deprecated alias for database | Deprecated alias for collection | Upsert existing content (true/false/1/0) | Knowledge files to ingest (repeatable; type=knowledge) | Per-document metadata as a JSON array (type=knowledge). Per item: metadata <= 16 KiB, additional_metadata <= 1 KiB. | App-knowledge items as a JSON array (type=knowledge). Per item: metadata <= 16 KiB, additional_metadata <= 1 KiB, optional acl principal list (PRO-1684). | Memory items as a JSON array (type=memory). Per item: metadata <= 16 KiB, additional_metadata <= 1 KiB. | Optional bring-your-own-graph payload as JSON
App-knowledge items as a JSON array (type=knowledge). Per item, metadata is capped at 16 KiB and additional_metadata at 1 KiB, measured on the compact JSON encoding of the whole map in UTF-8 bytes (keys and punctuation count). The deprecated tenant_metadata / document_metadata spellings are accepted here and held to the same caps. Over-cap returns 400 with the actual byte count. Each item may also carry acl, a list of principals (user_email:<email>, a bare email, group:<provider>:<id>, domain:<domain>, or __public__) restricting who may retrieve it; omit it to leave the document unrestricted, and send an empty list to restrict it to nobody. A malformed principal rejects the whole request with 400.
Per-document metadata as a JSON array (type=knowledge). Per item, metadata is capped at 16 KiB and additional_metadata at 1 KiB. Both caps are measured on the compact JSON encoding of the whole map in UTF-8 bytes, so keys, quotes, commas and braces count toward the budget. Over-cap returns 400 with the actual byte count.
Memory items as a JSON array (type=memory). Per item, metadata is capped at 16 KiB and additional_metadata at 1 KiB, measured on the compact JSON encoding of the whole map in UTF-8 bytes (keys and punctuation count). Over-cap returns 400 with the actual byte count.
knowledge, memory Response
Accepted
Show child attributes
Show child attributes
{
"failed_count": 0,
"message": "Success",
"results": [
{
"error": "",
"filename": "policy.pdf",
"id": "HydraDoc1234",
"infer": true,
"relations_created": 5,
"status": "queued",
"title": "Project Phoenix Overview"
}
],
"success": true,
"success_count": 2
}
Error message, empty string on success.
Show child attributes
Show child attributes
{
"code": "DATABASE_NOT_FOUND",
"message": "Database not found"
}
Show child attributes
Show child attributes
{
"collection": "team_docs",
"database": "acme_corp",
"latency_ms": 12.3,
"request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
"source_type": "file",
"sub_tenant_id": "sub_tenant_4567",
"tenant_id": "tenant_1234"
}
Whether the request succeeded.
true
Was this page helpful?
