POST /context/ingest to ingest data from your apps. Each message, ticket, page, email, meeting, or record is one app source.
HydraDB organizes these items and their relationships, indexes them, and lets you retrieve them through POST /query.
This guide builds up in order:
- Ingest an app source: one request, shown with a Slack message.
- Classes: the class names, and how to map your app’s data to class fields.
- App patterns: ready-to-adapt examples for messaging, tickets, wikis, email, meetings, and CRM apps.
- Relate messages, tickets, and records: threads, replies, comments, and links between items, including across apps.
- Incremental ingestion: adding, updating, and deleting items as your app changes.
- Attachments and comments, metadata, and querying.
1. Ingest an app source
The example below ingests one Slack message. Every app uses this same request; only the item insideapp_knowledge changes. The app patterns show the item for each app.
The request is multipart/form-data with these fields:
Ingest a Slack message: Python, TypeScript, or cURL
Ingest a Slack message: Python, TypeScript, or cURL
What each property in the item means
thread_id and the other fields that link items together.
Updates and status
To change an item you already ingested, send it again with the sameid and add upsert=true. This replaces the saved item, so include everything you want to keep. To add new items over time, such as new replies in a thread, follow incremental ingestion.
The response lists each item’s id in data.results. Check progress with GET /context/status, and wait for indexing_status: "completed" before querying the item or its connections.
2. Classes: tell HydraDB what each item is
A class tells HydraDB what kind of item you are sending. Set it inkind, repeat it in fields.kind, name the app in provider, and put the item’s content in fields. HydraDB supports seven classes:
ticket and their comments as comment. A Gong connector sends call recordings as meeting and the library items and scorecard reviews about those calls as knowledge_base. Each item carries its own class. The ticket pattern sends a ticket and its comment in one request, and the meeting pattern shows a Gong call.
Map your app’s data to the class fields
Your connector fetches data from the app. For each item, put its text, people, dates, and app IDs into the fields of its class. If you want attachment text to be searchable, extract it too and send it inattachments. This is how a Slack API message maps to a message item:
fields.title and fields.description. An email maps its subject, body, sender, and recipients to the email fields. A meeting maps its transcript or notes to fields.body and its host and participants to the meeting fields. The app patterns show a complete item for each app, and the field reference lists every field per class. Keep the app’s own ID in external_id so later updates and connections refer to the same item.
Map your data to the custom class
Use thecustom class for records that do not fit another class, such as CRM deals, contacts, invoices, database rows, or records from your internal app. Set both kind and fields.kind to custom. Your business record type, such as opportunity or invoice, goes in fields.data.record_type.
Your connector maps the record’s properties to fields.data and its readable text to fields.body.
Salesforce opportunity: map to custom and link to a Gmail email
Salesforce opportunity: map to custom and link to a Gmail email
type=knowledge. custom is the item’s class; opportunity is the business record type in this example.Put the app’s business fields in fields.data as a JSON object. HydraDB converts these values to searchable text, leaving out empty values, 0, and false. Use fields.body for a readable description or notes, including zero or false values you want to search.Map the Salesforce opportunity’s fields to these context fields, and connect the resulting item to its account and to the Gmail email where the customer’s renewal terms were discussed:external_id: "account_456". For the target.source_id relation shown here, ingest the Gmail email from the email pattern and wait for completed before ingesting the opportunity. HydraDB uses the account as the opportunity’s parent and the email as a linked item.Keep connection fields outside data: fields.parent_id links to a parent item in the same app, fields.thread_id groups a conversation, and relations connects items, including across apps. Storing an ID in fields.data.account_id alone does not create a link. Relate messages, tickets, and records explains each of these.For filters such as deal stage, also map the property into metadata and declare it in your metadata schema with enable_match: true. Keys in fields.data provide searchable content; they do not automatically become metadata filters.After ingestion, search with type: "knowledge" and query_apps: true, just as for other classes. For example, ask “What is blocking Acme’s renewal?” with mode: "thinking" to include linked items. When the deal changes, send the complete updated item with the same IDs and upsert=true. Keep its links and other content. See incremental ingestion.Item IDs
Each item has two IDs:idis your stable HydraDB ID. Use it to check status, replace the item, or delete it. For example,slack_C01_1716213600_000100.external_ididentifies the item in the app. For example, the Slack message timestamp1716213600.000100or Jira issue keyAUTH-123. Parent and reply fields refer to this ID.thread_iduses the app’s conversation ID instead: in Slack, that is the opening message’sexternal_id, and in Gmail, it is the thread ID shared by every email in the conversation.
provider for items from the same app. HydraDB matches external IDs within that provider, database, and collection. If two channels, workspaces, or mailboxes can have the same item ID, include their name or ID in external_id to tell the items apart. Use that same ID format in parent, reply, and relation fields. Adding a channel name to metadata does not change how IDs match.
database and collection say where the item belongs. Include them in each item, matching the values on the ingestion request. title and url give the user a readable citation and a link back to the original item.
3. Choose your app pattern
These six patterns cover messages, tickets and comments, pages, emails, meetings, and business records. They also work for other apps with the same kinds of items. Open a pattern, replace its example values with your app’s data, and send it throughapp_knowledge using the ingestion request. To update an existing item later, keep its IDs and add upsert=true.
The connection fields used in these patterns, such as thread_id, parent_id, and relations, are explained in Relate messages, tickets, and records.
Slack / Teams: threads, replies, attachments
Slack / Teams: threads, replies, attachments
message for both the opening message and every reply. A reply has its own ID and body. All messages in the thread share fields.thread_id; each reply also sets fields.parent_id to the opening message’s external_id.In Slack, the message ID is its ts, and the thread root is thread_ts. For the opening message, use its own ts as thread_id. The example uses one channel; keep IDs different across the channels and workspaces you sync (see Item IDs).reply_to connection from parent_id. The opening message has no parent_id. To add a reply, send a new item without resending the opening message, as shown in incremental ingestion. Use the same fields for other messaging apps, with their own message and conversation IDs.Jira / Linear / Zendesk: tickets and comments
Jira / Linear / Zendesk: tickets and comments
ticket for an issue; comment for each comment you send separately. The ticket’s main text is fields.description. A comment has its own external_id, and its fields.parent_id is the ticket’s external_id with the same provider. This is the one-connector, two-classes case: both items below come from Jira.child_of connection from AUTH-123 to its epic, a linked_to connection to AUTH-124, and a comment_on connection from c123 to AUTH-123. Ingest the epic and linked issue too to make their content available. For Linear, Zendesk, or GitHub issues, change the provider and use that app’s item IDs.Notion / Confluence: pages and parents
Notion / Confluence: pages and parents
knowledge_base. Ingest one item per page. Use fields.parent_id for another page’s ID, and put the workspace or space in metadata.child_of connection to page_engineering. Ingest that parent page separately. To edit the runbook, send the complete updated page with the same HydraDB ID and upsert=true.Gmail / Outlook: emails and replies
Gmail / Outlook: emails and replies
email for each email, including replies. Use the shared conversation ID as fields.thread_id, and the email being replied to as fields.reply_to_id.19a1a00 separately with the same thread_id. HydraDB creates the reply_to connection. A reply is still an email item, with its own sender, recipients, subject, and body.For an opening email, leave out reply_to_id. For a forwarded email, set fields.forwarded_from_id to the original email’s external ID in the same app, and ingest the original email too. Put any CC and BCC recipients in fields.cc and fields.bcc as lists of email addresses.Gong / Fireflies / Zoom: meetings and transcripts
Gong / Fireflies / Zoom: meetings and transcripts
meeting for each recorded call or meeting. Put the transcript or notes in fields.body, the host in fields.host and fields.host_email, and the other people in fields.participants. HydraDB recognizes the host and participants as people, so questions such as “What did Morgan ask for on the renewal call?” can find this item.name and email. Put the full transcript in fields.body; HydraDB indexes it as the meeting’s content. Use fields.labels for topics or tags from the app. For Fireflies or Zoom, change the provider and use that app’s meeting ID as external_id.A Gong connector also sends library items and scorecard reviews about a call as knowledge_base items, with the call’s ID in fields.parent_id. That is the same one-connector, two-classes pattern as Jira’s tickets and comments.Salesforce / HubSpot: CRM and app relations
Salesforce / HubSpot: CRM and app relations
custom. Put the record’s business fields in fields.data. Use fields.parent_id for the account’s ID in the same CRM, and relations for connections to items in other apps. This example links the opportunity to the Gmail email from the email pattern.account_456 as another CRM item. Ingest the Gmail email first and wait for completed, because the relation refers to its HydraDB ID. The description in fields.body makes the reason for that link searchable.See Map your data to the custom class for mapping your own business fields and adding searchable text.4. Relate messages, tickets, and records
This section explains how items connect to each other. Most connections come from fields you already send in the patterns above:thread_id and parent_id on a message, parent_id on a comment, reply_to_id on an email, and linked_issue_ids on a ticket. HydraDB turns these into connections when it ingests the item. For connections those fields cannot express, such as a Slack reply about a Jira ticket, add a relations list.
Open a topic to see the fields, how HydraDB uses them, and a request you can adapt.
Threads and replies: Slack / Gmail
Threads and replies: Slack / Gmail
message item describe this:fields.thread_idsays which conversation the message belongs to. Every message in the thread, including the opening message, uses the opening message’sexternal_idhere.fields.parent_idsays which message this one replies to. A reply sets it to the opening message’sexternal_id. The opening message leaves it out.
thread_id, and HydraDB creates a reply_to connection from each reply to the opening message. Searching with query_apps: true and mode: "thinking" then returns a thread’s replies together with its opening message. To add a reply later, send only the new reply, as shown in incremental ingestion.For other apps, use their conversation IDs. Email uses thread_id to group the conversation and reply_to_id to identify the email being replied to. Ticket comments use parent_id to identify the ticket; they can also carry a thread_id when the app has a discussion thread.Relationships from fields: Jira / Notion
Relationships from fields: Jira / Notion
Explicit relations: Slack to Jira
Explicit relations: Slack to Jira
relations to describe connections beyond those fields: a Slack discussion about a Jira incident, a CRM renewal tied to a support ticket, or an issue blocking a release.A relation reads as this item, then the relationship, then the target. On Carol’s Slack reply, the following addition means “this message is linked to Jira ticket AUTH-123”:predicate names the relationship. target identifies the other item. Optional properties are accepted in the request, but are not saved on the connection or used as search filters. Put a reason you want to search in the item’s text. Put values you want to filter on in metadata.Identify the other item in either of these ways:target.source_id, ingest that item and wait for completed before adding the relation.Choose the direction that matches the relationship. These are common names; expand “Your own relations: Jira to Slack” below to define your own:query_apps: true and mode: "thinking" to include related items.Your own relations: Jira to Slack
Your own relations: Jira to Slack
predicate to name a relationship from your business data. For example, use verified_by to connect a Jira ticket to a Slack verification message, or renews to connect a new contract to an earlier contract. You do not need to register a relation type first.A custom relationship works with any class. The Jira ticket below is still kind: "ticket"; you do not need the custom class to use your own relation name. Use the same non-empty name on each sync, such as verified_by. In this example, the direction is from the ticket to the message: the ticket is verified by the message.First ingest Carol’s Slack reply from the messaging pattern and wait for completed. Then send the complete Jira ticket below with the new relationship. Use upsert=true because the ticket from the ticket pattern already exists.external_id and provider, as shown in “Explicit relations: Slack to Jira” above.When updating the item, send the complete item. Keep its other fields, metadata, attachments, comments, and relations. Include your custom relation on later syncs too.Search: Use query_apps: true and mode: "thinking" to include related items. A custom name describes the link; it does not create a search filter or guarantee that a question will follow that link. To request the message’s text with a ticket result, put the message’s HydraDB ID in relations.ids and your named relationships in app_relations. See “Linked query context: Gmail to Notion” below for the request, response, and current limitation.Linked query context: Gmail to Notion
Linked query context: Gmail to Notion
relations.ids to request linked content with a search result. For example, when a Gmail email matches a question, include the linked Notion runbook’s text too.POST /query, use mode: "thinking" and query_forceful_relations: true (the default). Also set query_apps: true to search app fields and connections. Fast mode ignores query_forceful_relations. This option follows links from relations.ids; a list of named relationships alone does not request this linked content.The response contains matching text in data.chunks and linked text in data.additional_context. Read both when showing results or building an answer. data.forceful_relations.declared shows which items were linked, using via.from and via.to.This option follows one link from each item found in the main search results. max_results: 4 limits the main results to four text chunks, or pieces of item text; it does not follow four links in a row. If A links to B and B links to C, a result from A can include B’s text, but does not fetch C through B. Linked results also have size limits and follow the request’s filters, database, collection, and access-control settings.Across apps: Gmail to Notion
Across apps: Gmail to Notion
source_id target, ingest the target first and wait for its ingestion to reach completed. Then add a linked_to relation to the referring item with the target’s HydraDB ID as target.source_id. For example, link gmail_msg_19a2b3c to notion_page_abc from the patterns above.Send this email through app_knowledge using the ingestion request. If you already ingested it, add upsert=true to update it. Replace the example IDs and fields with the email’s actual values.id identifies it in HydraDB. target.source_id is the Notion page’s HydraDB ID, which you can obtain from ingestion results or item listing. A target identified by source_id does not require provider.To add the link to an existing email, send the complete updated item with upsert=true. Keep its fields, metadata, attachments, comments, and other relations. Include the relation on later syncs too.When you only have the app’s external ID, use target.external_id and the target’s stored provider name. To target the Gmail email from the Notion page instead, the target would be:provider: "gmail" is stored under google. Gmail external-ID targets accept gmail as well as google; the example uses the stored name. Prefer source_id when you know the HydraDB ID; it avoids provider-name matching.The example includes both a named relationship and relations.ids. Search with query_apps: true, mode: "thinking", and query_forceful_relations: true to include linked content from either side: an email result can include the runbook, and a runbook result can include the email. Add the link on one item; you do not need to repeat it on the other. Ingest both items first. To link a conversation, link to an email you have ingested and keep its thread fields.When items identify people by email address, HydraDB can recognize the same person across apps. To say that a particular email is about a particular page, add a relation between those items.Foreign-key links: HubSpot to Postgres
Foreign-key links: HubSpot to Postgres
psql_user_id: "123" referring to row 123 in your Postgres public.users table. Ingest the contact and the row with the custom class. Your mapping code reads the field and adds a relation to the row’s HydraDB ID.psql_user_id, find the row’s HydraDB ID or build it using the ID format above, and put it in app_relations and relations.ids, as shown below. Storing the user ID in fields.data alone does not create a link. Search uses the data you ingested; it does not read from Postgres or check a database foreign-key rule.Declare user_id as a VARCHAR field with enable_match: true in your database metadata schema. The example below shows both items. Ingest the row by itself with the ingestion request and wait for completed. Then ingest the contact that links to it.completed, then send the contact; putting them together in a list does not ensure this order. To link by external ID instead, use { "external_id": "public.users:123", "provider": "postgres" } in app_relations[].target. relations.ids still uses the row’s HydraDB ID.To include the linked row, search with query_apps: true, mode: "thinking", and query_forceful_relations: true. Read additional_context for the row’s text, along with the contact result in chunks. Fast mode ignores query_forceful_relations. The link limitation described in “Linked query context: Gmail to Notion” also applies here.To search only for user 123, use metadata_filters. Put metadata.user_id on both items so both can pass the filter. Filters also apply to linked content. Search returns the best matches among the items that pass.Send this JSON body to POST /query:psql_user_id changes, ingest the new row and wait for completed. Then send the complete contact with upsert=true, updating app_relations[].target.source_id, relations.ids, and metadata.user_id. Keep the same row IDs when editing the row’s other fields. After the contact completes, check the changed link with Get connected items.5. Incremental ingestion
Run your connector regularly or when new app events arrive. After the initial ingestion, send the new items discovered on each sync toPOST /context/ingest. Each new item gets its own stable HydraDB ID.
For Slack, adding a reply means ingesting a new message item in the existing thread. Keep the shared thread_id, set parent_id to the opening message’s external ID, and give the reply its own IDs and body. These are the same two fields explained under “Threads and replies” in Relate messages, tickets, and records.
Add a Slack reply to an existing thread: Python, TypeScript, or cURL
Add a Slack reply to an existing thread: Python, TypeScript, or cURL
slack_C01_1716213600_000100, with external_id: "1716213600.000100". Bob now posts another reply. Ingest it using the request below.Bob’s reply has its own id and external_id. Its thread_id groups it with Alice’s message, and its parent_id points to Alice’s external ID. Send only this new reply on this sync.data.results for Bob’s new HydraDB ID, then check its progress with GET /context/status. After completed, the new reply is part of the existing discussion and its thread connection is in place.comment item with its own IDs and the ticket’s external ID in fields.parent_id. Your connector keeps track of what it has already sent, using the app’s last-seen event ID, page token, or update time. HydraDB does not fetch the next set of items for you.
Edit or delete an existing item: Slack example
Edit or delete an existing item: Slack example
completed or errored before sending another update. Send the complete updated reply below with upsert=true. This replaces Carol’s saved context, keeping the same IDs and thread connections.data.results, then follow the same HydraDB ID with GET /context/status. Wait for completed before verifying the edit or its connections.For edits and deletions across apps:upsert=true replaces the saved context for that item, so include all fields, metadata, attachments, comments, and relations you want to keep. Send updates for the same item one at a time: wait for completed or errored before sending the next update. If it errored, read error_code and fix the item before retrying. Send each new ticket comment as a separate comment item if you want to update or delete it without resending the ticket.For deletion, check the response’s data.results[].deleted value. An HTTP 200 alone can include a failed deletion when the item is still processing; wait for completed or errored and retry. Set X-HydraDB-Delete-Status: strict to receive an HTTP error for a failed deletion. See Delete item.6. Include attachments and comments
Attachments: the supporting file’s text
A message can share a runbook; a ticket comment can include a diagnostic trace. Add these to the item’sattachments array so their extracted text is searchable and connected to the item that shared them.
Extract the file’s text in your connector and send it in content.text or content.markdown. url links back to the file; HydraDB does not download it from this link. To upload the original file for HydraDB to read, use file ingestion.
Attachment example: add a diagnostic trace to a comment
Attachment example: add a diagnostic trace to a comment
Comments: include them on the parent or send them separately
There are two ways to include comments. Choose based on how your app sync works:comments field are saved with the parent. Their text and authors can be searched. Separate comment items have their own HydraDB IDs, so you can update or delete each one without resending the parent.
Jira example: include all current comments on a ticket
Jira example: include all current comments on a ticket
upsert=true, send all its current comments. Sending only the newest comment replaces the saved list; it does not add to it. To send comments separately, use the ticket-and-comment pattern above.message and email classes. This preserves each reply’s app fields and thread connections.
7. Add metadata for filtering and display
Usemetadata for shared values you want to filter on, such as Slack channel, Jira project, or department. Declare those keys in the database metadata schema with enable_match: true before ingestion.
Use additional_metadata for other details you want to keep, such as the app’s event ID or a display label.
metadata_filters: { "channel": "engineering" }. Filters for additional_metadata go under metadata_filters.additional_metadata. See Scoping using metadata for schema setup and complete filter examples.
Private app data: set who can see each item
Private app data: set who can see each item
acl field. For example, add this to an item Alice may read:POST /query request:acl. A query without it is not filtered by item permissions. An item without acl is unrestricted; an empty permission list, "acl": [], allows nobody when permissions are checked. Your connector supplies the item’s allowed people and groups, and your application supplies the caller’s identity on each query. See Access Control for groups, domains, and updating permissions.8. Query app sources
Search withPOST /query, using type: "knowledge" (or "all") and query_apps: true. Set mode: "thinking" to include related items, such as a thread’s replies or a ticket’s comments. For links in relations.ids, also use query_forceful_relations: true and read additional_context. Open “Linked query context: Gmail to Notion” under Relate messages, tickets, and records for the fields and current limitation.
Query example: retrieve the rollback discussion and its replies
Query example: retrieve the rollback discussion and its replies
Field reference
Open the fields for the class you are using.kind chooses the class; provider names the app.
Top-level context fields
Top-level context fields
email fields
email fields
message fields
message fields
ticket fields
ticket fields
knowledge_base fields
knowledge_base fields
comment fields
comment fields
meeting fields
meeting fields
custom fields
custom fields
Relation fields
Relation fields
Attachment fields
Attachment fields
Related
- Knowledge: app sources and file uploads
- Query: retrieving context
- Scoping using metadata: database schemas and filters
- Access Control: restricting who may retrieve an item
- Ingest Content: request and response reference
- Ingestion Status: following processing progress

fields.kind.comment_onlink to the parent.