import requests
url = "https://api.hydradb.com/ingestion/upload_knowledge"
files = { "files.items": ("example-file", open("example-file", "rb")) }
payload = {
"tenant_id": "tenant_1234",
"sub_tenant_id": "sub_tenant_4567",
"upsert": "true",
"file_metadata": "<string>",
"app_knowledge": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('tenant_id', 'tenant_1234');
form.append('sub_tenant_id', 'sub_tenant_4567');
form.append('upsert', 'true');
form.append('file_metadata', '<string>');
form.append('app_knowledge', '<string>');
form.append('files.items', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.hydradb.com/ingestion/upload_knowledge', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));curl --request POST \
--url https://api.hydradb.com/ingestion/upload_knowledge \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form tenant_id=tenant_1234 \
--form sub_tenant_id=sub_tenant_4567 \
--form upsert=true \
--form 'file_metadata=<string>' \
--form 'app_knowledge=<string>' \
--form files.items='@example-file'{
"success": true,
"message": "Upload initiated successfully",
"results": [],
"success_count": 1,
"failed_count": 1
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}Upload Knowledge
Ingest documents (files) and/or app-generated content (app knowledge) into a tenant.
import requests
url = "https://api.hydradb.com/ingestion/upload_knowledge"
files = { "files.items": ("example-file", open("example-file", "rb")) }
payload = {
"tenant_id": "tenant_1234",
"sub_tenant_id": "sub_tenant_4567",
"upsert": "true",
"file_metadata": "<string>",
"app_knowledge": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('tenant_id', 'tenant_1234');
form.append('sub_tenant_id', 'sub_tenant_4567');
form.append('upsert', 'true');
form.append('file_metadata', '<string>');
form.append('app_knowledge', '<string>');
form.append('files.items', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.hydradb.com/ingestion/upload_knowledge', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));curl --request POST \
--url https://api.hydradb.com/ingestion/upload_knowledge \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form tenant_id=tenant_1234 \
--form sub_tenant_id=sub_tenant_4567 \
--form upsert=true \
--form 'file_metadata=<string>' \
--form 'app_knowledge=<string>' \
--form files.items='@example-file'{
"success": true,
"message": "Upload initiated successfully",
"results": [],
"success_count": 1,
"failed_count": 1
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}{
"detail": {
"success": true,
"message": "Error occurred",
"error_code": "<string>"
}
}When to use it
Use this endpoint to add knowledge that your agents will recall later:- Files – PDFs, DOCX, Markdown, CSVs, and other documents you want HydraDB to parse and chunk
- App knowledge – structured content from connected apps (Slack messages, Notion pages, Gmail threads, tickets, CRM records, etc.) where you already have the text, IDs, metadata, and relations, sent as the
app_knowledgemultipart field (JSON string)
POST /memories/add_memory instead.
Endpoint
- Auth: Bearer token
- Content type:
multipart/form-data(always – even when sending onlyapp_knowledge) - Idempotency: Controlled by
upsert(defaulttrue– overwrites existing items with the same ID) - Async: Yes – returns
200with a JSON body (SourceUploadResponsein OpenAPI) including onesource_idper queued item. UsePOST /ingestion/verify_processingto check status.
api-reference/openapi.json and uses the multipart field name app_knowledge, matching the OpenAPI spec.Two ingestion paths
This endpoint accepts two content formats in one request. Send either or both:| Path | Form field | Content type |
|---|---|---|
| Files | files (binary) + optional file_metadata (JSON string) | Documents to be parsed |
| App sources | app_knowledge (JSON string) | Pre-parsed app content with metadata |
Example: Upload files
curl -X POST 'https://api.hydradb.com/ingestion/upload_knowledge' \
-H "Authorization: Bearer <your_api_key>" \
-F "tenant_id=my_first_tenant" \
-F "files=@/path/to/contract.pdf" \
-F "files=@/path/to/policy.pdf" \
-F 'file_metadata=[
{ "file_id": "contract_q4", "metadata": { "category": "legal" } },
{ "file_id": "policy_2025", "metadata": { "category": "legal" } }
]'
const response = await client.upload.knowledge({
tenant_id: "my_first_tenant",
files: [
{ path: "/path/to/contract.pdf", filename: "contract.pdf", contentType: "application/pdf" },
{ path: "/path/to/policy.pdf", filename: "policy.pdf", contentType: "application/pdf" },
],
file_metadata: JSON.stringify([
{ file_id: "contract_q4", metadata: { category: "legal" } },
{ file_id: "policy_2025", metadata: { category: "legal" } },
]),
});
import json
with open("/path/to/contract.pdf", "rb") as f1, \
open("/path/to/policy.pdf", "rb") as f2:
response = client.upload.knowledge(
tenant_id="my_first_tenant",
files=[
("contract.pdf", f1, "application/pdf"),
("policy.pdf", f2, "application/pdf"),
],
file_metadata=json.dumps([
{"file_id": "contract_q4", "metadata": {"category": "legal"}},
{"file_id": "policy_2025", "metadata": {"category": "legal"}},
]),
)
Example: Upload app knowledge
curl -X POST 'https://api.hydradb.com/ingestion/upload_knowledge' \
-H "Authorization: Bearer <your_api_key>" \
-F "tenant_id=my_first_tenant" \
-F 'app_knowledge=[
{
"tenant_id": "my_first_tenant",
"sub_tenant_id": "my_first_tenant",
"id": "slack_msg_12345",
"title": "Q4 planning standup notes",
"type": "slack",
"url": "https://acme.slack.com/archives/C01/p1234567890",
"timestamp": "2025-10-15T14:30:00Z",
"kind": "message",
"provider": "slack",
"external_id": "1234567890.000100",
"fields": {
"kind": "message",
"body": "Today we discussed the Q4 roadmap...",
"author": "alex",
"thread_id": "1234567890.000100",
"url": "https://acme.slack.com/archives/C01/p1234567890"
},
"metadata": {
"container_type": "channel",
"container_id": "C01",
"container_name": "planning",
"category": "engineering"
},
"attachments": [
{
"id": "att-1",
"title": "roadmap-notes.txt",
"content_type": "text/plain",
"content": { "text": "Attached notes mention owners for the Q4 roadmap." }
}
]
}
]'
const response = await client.upload.knowledge({
tenant_id: "my_first_tenant",
app_knowledge: JSON.stringify([
{
tenant_id: "my_first_tenant",
sub_tenant_id: "my_first_tenant",
id: "slack_msg_12345",
title: "Q4 planning standup notes",
type: "slack",
url: "https://acme.slack.com/archives/C01/p1234567890",
timestamp: "2025-10-15T14:30:00Z",
kind: "message",
provider: "slack",
external_id: "1234567890.000100",
fields: {
kind: "message",
body: "Today we discussed the Q4 roadmap...",
author: "alex",
thread_id: "1234567890.000100",
url: "https://acme.slack.com/archives/C01/p1234567890",
},
metadata: {
container_type: "channel",
container_id: "C01",
container_name: "planning",
category: "engineering",
},
attachments: [
{
id: "att-1",
title: "roadmap-notes.txt",
content_type: "text/plain",
content: { text: "Attached notes mention owners for the Q4 roadmap." },
},
],
},
]),
});
import json
response = client.upload.knowledge(
tenant_id="my_first_tenant",
app_knowledge=json.dumps([
{
"tenant_id": "my_first_tenant",
"sub_tenant_id": "my_first_tenant",
"id": "slack_msg_12345",
"title": "Q4 planning standup notes",
"type": "slack",
"url": "https://acme.slack.com/archives/C01/p1234567890",
"timestamp": "2025-10-15T14:30:00Z",
"kind": "message",
"provider": "slack",
"external_id": "1234567890.000100",
"fields": {
"kind": "message",
"body": "Today we discussed the Q4 roadmap...",
"author": "alex",
"thread_id": "1234567890.000100",
"url": "https://acme.slack.com/archives/C01/p1234567890",
},
"metadata": {
"container_type": "channel",
"container_id": "C01",
"container_name": "planning",
"category": "engineering",
},
"attachments": [
{
"id": "att-1",
"title": "roadmap-notes.txt",
"content_type": "text/plain",
"content": {"text": "Attached notes mention owners for the Q4 roadmap."},
}
],
}
]),
)
Form fields
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
tenant_id | string | Yes | – | The tenant to ingest into. |
files | binary[] | One of | [] | One or more files to upload. |
file_metadata | JSON string | No | – | Array of metadata objects, one per file. Length must match files. See File metadata. |
app_knowledge | JSON string | One of | – | A single app source object or an array. Each item must include tenant_id and sub_tenant_id. See App knowledge. |
app_sources | JSON string | - | – | Deprecated alias for app_knowledge. Use app_knowledge instead. |
sub_tenant_id | string | No | the tenant_id value | Sub-tenant scope. When omitted, content is stored under the default sub-tenant, which is the tenant_id value - not the literal string "default". |
upsert | boolean | No | true | If true, existing sources with the same ID are overwritten. |
files or app_knowledge. You can send both in the same request.
File metadata
Each entry infile_metadata corresponds to the file at the same index in files:
| Field | Type | Description |
|---|---|---|
file_id | string | Optional. Server-generated if omitted. Use this to reference the file later (e.g., for verify_processing). |
metadata | object | Tenant-level metadata. Pre-filterable in recall via top-level keys in metadata_filters (fast). Each key must be declared in tenant_metadata_schema with enable_match: true. |
additional_metadata | object | Document-level metadata. Filterable in recall by nesting under "additional_metadata" (canonical) or "document_metadata" (legacy alias) inside metadata_filters. Post-retrieval, so slower than tenant-level filters (engine over-fetches ~3x to compensate). See Full recall → Metadata filters. |
relations | object | Forcefully connect this file to other sources. See Forceful relations. |
App knowledge
Each item in theapp_knowledge JSON array represents a single piece of pre-parsed content. tenant_id and sub_tenant_id are required inside each item.
For app-aware retrieval, include the typed app-source fields below. See App Sources for field-by-field guidance, relation examples, and search behavior.
| Field | Type | Description |
|---|---|---|
tenant_id | string | Required. The tenant to ingest into. |
sub_tenant_id | string | Required. Sub-tenant scope. If not partitioning by user, use the same value as tenant_id - that is the default sub-tenant an omitted sub_tenant_id resolves to on queries. Do not use the literal string "default"; it creates a separate scope that omitted-scope queries will never match. |
id | string | Required. Unique identifier for the source. |
title | string | Short title or subject. |
type | string | Source category (e.g., slack, notion, gmail, webpage). |
description | string | Optional long-form description. |
url | string | Canonical URL or reference link. |
timestamp | string | ISO-8601 timestamp (creation or last-updated). |
content | object | Legacy/generic content payload. For typed app sources, prefer fields for item text. See Content formats. |
metadata | object | Tenant-level or app-specific metadata. Use this for category, channel, project, space, workspace, or other provider context. Filterable at recall via top-level keys in metadata_filters. |
additional_metadata | object | Document-level metadata. Stored per-source. Filterable at recall by nesting under "additional_metadata" (canonical) or "document_metadata" (legacy alias) in metadata_filters. Post-retrieval (the engine over-fetches and filters). See Full recall → Metadata filters. |
relations | array | Typed app-source relations such as reply_to, child_of, linked_to, or blocks. |
kind | string | App object kind: email, message, ticket, knowledge_base, or custom. |
provider | string | Provider namespace such as slack, gmail, jira, notion, or salesforce. |
external_id | string | Stable provider ID for this source. Used for exact ID search and relation resolution. |
fields | object | Kind-specific structured fields. Include kind inside this object. |
attachments | array | Attachments connected to this app source. Include content.text or content.markdown when you already have extracted attachment text. |
comments | array | Comments connected to this app source. |
search_apps: true use graph expansion and exact provider IDs during Recall.Content formats
For typed app sources, primary searchable text belongs infields, such as fields.body for messages/emails or fields.description for tickets. The top-level content object remains available for legacy or untyped app knowledge payloads.
The content object supports multiple content types. Use the field that matches your source - only one needs to be set:
| Field | Use for |
|---|---|
text | Plain text content |
markdown | Markdown-formatted content |
html_base64 | Base64-encoded HTML |
csv_base64 | Base64-encoded CSV |
{ "content": { "text": "Plain text content." } }
content object. HydraDB indexes attachments[].content.text and, when text is omitted, attachments[].content.markdown; attachment URLs and titles are metadata and are not fetched or parsed automatically.
Response
{
"success": true,
"message": "Upload initiated successfully",
"results": [
{
"source_id": "ef3ea754019855e2b39e9ab5c2d26096",
"filename": "contract.pdf",
"status": "queued",
"error": null
},
{
"source_id": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
"filename": "policy.pdf",
"status": "queued",
"error": null
}
],
"success_count": 2,
"failed_count": 0
}
| Field | Description |
|---|---|
success | true if at least one item was queued. |
message | Human-readable status. |
results[] | Per-item upload result. |
results[].source_id | The ID assigned to this source. Use this for verify_processing and downstream operations. |
results[].filename | Original filename (for files) or null (for app knowledge payloads). |
results[].status | Initial status. Always queued on a successful response. |
success_count / failed_count | Aggregate counts across the request. |
Behavior notes
POST /ingestion/verify_processing with the returned source_ids to check when content is fully indexed.app_knowledge (no files), the endpoint expects multipart/form-data. Sending JSON directly will return 422 VALIDATION_ERROR.file_metadata and app_knowledge are JSON strings. Both must be serialized before being sent as form fields. Most HTTP libraries don’t auto-stringify nested JSON in multipart bodies.Related endpoints
- Next: Verify processing – poll for ingestion status
- Next: Full recall – retrieve ingested content
- Alternative: Add memory – for user memories instead of knowledge
- Related: Delete knowledge – remove sources
Errors
Common codes:400 INVALID_PARAMETERS, 404 TENANT_NOT_FOUND, 422 VALIDATION_ERROR. See Error Responses for the full list.
Read more: Essentials → Memories · Essentials → MetadataAuthorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Unique identifier for the tenant/organization
"tenant_1234"
Optional sub-tenant identifier used to organize data within a tenant. If omitted, the default sub-tenant created during tenant setup will be used.
"sub_tenant_4567"
If true, update existing sources with the same id.
true
Files to upload (documents). Omit or leave empty when only sending app_knowledge.
JSON array of file metadata objects; length must match files when provided. Each object may include: file_id (optional), metadata, additional_metadata, and relations (forceful relations to other HydraDB source IDs).
JSON: single source object or array of app-generated sources to index. Omit when only uploading files.
Response
Successful Response
true
List of upload results for each source.
Show child attributes
Show child attributes
[]
Number of sources successfully queued.
1
Number of sources that failed to upload.
1
Was this page helpful?
