From 957e2ee41c6a792a184a370c59180c9809179af0 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Wed, 23 Sep 2026 18:29:48 +0530 Subject: [PATCH 01/17] docs: `context` is the ingest list field everywhere, regenerate the v2 spec (PRO-1618) Copy the spec generated by hydra-db/hydradb-application#1659 (commit 402c706c557b) to api-reference/v2/openapi.json, the same verbatim copy the auto-generate workflow makes. /context/ingest now publishes a `context` form field and marks `items` deprecated. The SDK examples send `context=` / `context:`, and the prose names the `context` form field. `items` is mentioned at most once per page, as the deprecated alias older SDK releases send. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 18 +- api-reference/v2/endpoint/ingest-context.mdx | 18 +- api-reference/v2/error-responses.mdx | 4 +- api-reference/v2/index.mdx | 2 +- api-reference/v2/openapi.json | 3731 ++++++++++++++++-- api-reference/v2/sdks.mdx | 8 +- essentials/v2/attributes.mdx | 6 +- essentials/v2/bring-your-own-graph.mdx | 6 +- essentials/v2/context-categories.mdx | 6 +- essentials/v2/databases-and-collections.mdx | 8 +- essentials/v2/ingest.mdx | 8 +- essentials/v2/split-databases.mdx | 2 +- get-started/v2/core-concepts.mdx | 2 +- get-started/v2/quickstart.mdx | 12 +- 14 files changed, 3394 insertions(+), 437 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index ad4a8885..ebb25b4c 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -316,7 +316,7 @@ SDK naming: - Python methods and fields: snake_case, for example `client.databases.collections()`, `max_results`, `query_by`, `result.data.llm_prompt`, `status.indexing_status`. - TypeScript methods and fields: camelCase, for example `maxResults`, `queryBy`, `pageSize`, `result.data.llmPrompt`, `chunk.chunkId`, `chunk.contextId`, `chunk.enrichmentKind`, `path.pathSummary`, `result.data.forcefulRelations`, `status.indexingStatus`. - Both SDKs return a `{ success, data, error, meta }` envelope; the payload is under `.data` (for example `response.data.infra`, `response.data.statuses`, `response.data.results`). -- `client.context.ingest()` sends a multipart form: the item list goes in the `items` form field as a JSON string. Keys inside each item stay snake_case in every language (`context_id`, `happened_at`, `custom_attributes`), because that string is raw wire data. +- `client.context.ingest()` sends a multipart form: the item list goes in the `context` form field as a JSON string. Keys inside each item stay snake_case in every language (`context_id`, `happened_at`, `custom_attributes`), because that string is raw wire data. --- @@ -346,11 +346,11 @@ while True: time.sleep(5) # 3. Ingest a policy into the shared collection and a conversation into Alex's. -# The SDK sends the item list as a JSON string in the `items` form field. +# The SDK sends the item list as a JSON string in the `context` form field. client.context.ingest( database=database, collection="company", - items=json.dumps([{ + context=json.dumps([{ "context_id": "refund-policy", "title": "Refund policy", "text": "Refunds are processed within 5 business days.", @@ -359,7 +359,7 @@ client.context.ingest( client.context.ingest( database=database, collection="user_alex", - items=json.dumps([{ + context=json.dumps([{ "context_id": "chat-alex-001", "conversation": [ {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, @@ -421,11 +421,11 @@ while (true) { } // 3. Ingest a policy into the shared collection and a conversation into Alex's. -// The SDK sends the item list as a JSON string in the `items` form field. +// The SDK sends the item list as a JSON string in the `context` form field. await client.context.ingest({ database, collection: "company", - items: JSON.stringify([{ + context: JSON.stringify([{ context_id: "refund-policy", title: "Refund policy", text: "Refunds are processed within 5 business days.", @@ -434,7 +434,7 @@ await client.context.ingest({ await client.context.ingest({ database, collection: "user_alex", - items: JSON.stringify([{ + context: JSON.stringify([{ context_id: "chat-alex-001", conversation: [ { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, @@ -727,9 +727,9 @@ Every key in `graph_payload` must equal the `context_id` of an item in the same - A validation error names the item it refers to as `context[N]`. - Ingest takes text only. To ingest a PDF, DOCX or CMS export, extract its text in your application and send it as `text`, one item per document. For tools such as Slack, Notion or Google Drive, use a [connector](/essentials/v2/connectors): synced content lands in the same database and is queried together with your items. -### SDKs: the `items` form field +### SDKs: the `context` form field -The SDKs send a multipart form rather than a JSON body. The item array goes in the `items` form field as a JSON string, next to `database`, `collection`, `upsert` and `graph_payload`; the server runs the same validation on both entry points. Set `enrich` and `instructions` on each item. Python: `client.context.ingest(database=..., collection=..., items=json.dumps([...]))`. TypeScript: `await client.context.ingest({ database, collection, items: JSON.stringify([...]) })`. Keys inside each item stay snake_case in both. Full examples are in [Minimal end-to-end flow](#4-minimal-end-to-end-flow). +The SDKs send a multipart form rather than a JSON body. The item array goes in the `context` form field as a JSON string, next to `database`, `collection`, `upsert` and `graph_payload`; the server runs the same validation on both entry points. Set `enrich` and `instructions` on each item. Python: `client.context.ingest(database=..., collection=..., context=json.dumps([...]))`. TypeScript: `await client.context.ingest({ database, collection, context: JSON.stringify([...]) })`. Keys inside each item stay snake_case in both. Full examples are in [Minimal end-to-end flow](#4-minimal-end-to-end-flow). SDK releases generated from the current API spec take `context`; older releases take `items`, which the API still accepts as a deprecated alias. ### Response diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index ed43f56a..a8bc2ee0 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -16,11 +16,11 @@ import { Field } from "/snippets/field.jsx"; ```python Python SDK import json -# The SDK sends a multipart form; the item list goes in the `items` form field. +# The SDK sends a multipart form; the item list goes in the `context` form field. result = client.context.ingest( database="acme_corp", collection="company", - items=json.dumps([ + context=json.dumps([ { "context_id": "refund-policy", "title": "Refund policy", @@ -44,11 +44,11 @@ print([r.id for r in result.data.results]) ``` ```typescript TypeScript SDK -// The SDK sends a multipart form; the item list goes in the `items` form field. +// The SDK sends a multipart form; the item list goes in the `context` form field. const result = await client.context.ingest({ database: "acme_corp", collection: "company", - items: JSON.stringify([ + context: JSON.stringify([ { context_id: "refund-policy", title: "Refund policy", @@ -106,15 +106,15 @@ curl -X POST 'https://api.hydradb.com/context/ingest' \ ## Request body -Send `application/json`. The SDKs send `multipart/form-data` instead: the same array goes in the `items` form field as a JSON string, and the request-level fields are form fields of the same name (`graph_payload` also as a JSON string). Both entry points run the same validation. Keys inside each item stay snake_case in every language. +Send `application/json`. The SDKs send `multipart/form-data` instead: the same array goes in the `context` form field as a JSON string, and the request-level fields are form fields of the same name (`graph_payload` also as a JSON string). Both entry points run the same validation. Keys inside each item stay snake_case in every language. -The SDK `ingest` methods take `database`, `collection`, `items`, `upsert` (the string `"true"` or `"false"`) and `graph_payload`. To set `enrich` or `instructions` through an SDK, set them on each item. +The SDK `ingest` methods take `database`, `collection`, `context`, `upsert` (the string `"true"` or `"false"`) and `graph_payload`. To set `enrich` or `instructions` through an SDK, set them on each item. SDK releases generated from the current API spec take `context`; older releases take `items`, which the API still accepts as a deprecated alias. | Name | Description | | --- | --- | | | Target database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | | | Collection inside the database. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=the default collection) | -| | The items to ingest, at least 1 and at most 100. In the multipart form the field is `items`, a JSON-encoded array. | +| | The items to ingest, at least 1 and at most 100. In the multipart form the field is also `context`, a JSON-encoded array. | | | Request-level default for every item's `enrich`. (default=`true`) | | | Request-level default for every item's `upsert`. (default=`true`) | | | Request-level default for every item's `instructions`. (default=empty) | @@ -193,7 +193,7 @@ import json client.context.ingest( database="acme_corp", collection="company", - items=json.dumps([ + context=json.dumps([ { "context_id": "billing-policy", "title": "Billing policy", @@ -219,7 +219,7 @@ client.context.ingest( await client.context.ingest({ database: "acme_corp", collection: "company", - items: JSON.stringify([ + context: JSON.stringify([ { context_id: "billing-policy", title: "Billing policy", diff --git a/api-reference/v2/error-responses.mdx b/api-reference/v2/error-responses.mdx index 3ffb62e1..e9b26936 100644 --- a/api-reference/v2/error-responses.mdx +++ b/api-reference/v2/error-responses.mdx @@ -179,7 +179,7 @@ try { await client.context.ingest({ database: "my_first_database", collection: "support", - items: JSON.stringify([ + context: JSON.stringify([ { context_id: "refund-policy", title: "Refund policy", text: "Refunds are processed within 5 business days." }, ]), }); @@ -211,7 +211,7 @@ try: client.context.ingest( database="my_first_database", collection="support", - items=json.dumps([ + context=json.dumps([ {"context_id": "refund-policy", "title": "Refund policy", "text": "Refunds are processed within 5 business days."}, ]), ) diff --git a/api-reference/v2/index.mdx b/api-reference/v2/index.mdx index af5de553..5575add0 100644 --- a/api-reference/v2/index.mdx +++ b/api-reference/v2/index.mdx @@ -156,7 +156,7 @@ Errors use the same envelope with `success: false`, `data: null`, and an `error` - **Pagination.** Listing endpoints (`/context/list`) return pagination fields for browsing large result sets. -- **Parameter casing.** The REST API uses snake_case (`max_results`). The Python SDK uses snake_case throughout; the TypeScript SDK uses camelCase for method names, parameters and response fields (`maxResults`, `llmPrompt`). Keys inside the JSON `items` string sent to `context.ingest` stay snake_case in every language. +- **Parameter casing.** The REST API uses snake_case (`max_results`). The Python SDK uses snake_case throughout; the TypeScript SDK uses camelCase for method names, parameters and response fields (`maxResults`, `llmPrompt`). Keys inside the JSON `context` string sent to `context.ingest` stay snake_case in every language. - **Query modes.** `POST /query` supports `query_by: "hybrid"` or `"text"` and `mode: "auto"`, `"fast"` or `"thinking"`. diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index e319a5b9..4e8a15a2 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -15,6 +15,14 @@ "description": "ACLFingerprint is the stable identity of the ACL last APPLIED to this\nresource's already-indexed documents (PRO-1684). The sync compares the\nfreshly-resolved provider ACL against it: equal means nothing to do,\ndifferent means fan the new ACL out to existing documents. Empty means\nnothing has been applied yet (first capture-enabled sync).", "type": "string" }, + "acl_warning": { + "description": "ACLWarning explains, in the provider's own words, why this resource's\npermissions could not be captured. Capture fails OPEN, so the resource\nis readable by everyone while this is set; without surfacing it, that\nwidening would be invisible to the person who turned RBAC on. Cleared\nautomatically by the next successful capture.", + "type": "string" + }, + "acl_warning_at": { + "description": "ACLWarningAt is when this warning last CHANGED (RFC3339). An unchanged\nwarning is not rewritten each cycle, so it reads as \"open since\".", + "type": "string" + }, "additional_metadata": { "additionalProperties": {}, "description": "AdditionalMetadata is merged into the additional_metadata (document\nmetadata) layer of every object synced from this resource. User-supplied\nkeys are shallow-merged as the base; provider-generated fields are\napplied on top and always win on conflict.", @@ -29,6 +37,10 @@ "example": 86400, "type": "integer" }, + "backfill_floor": { + "description": "BackfillFloor is the fixed oldest boundary the historical crawl is working\ntowards, stamped once at configure time as now-lookback_days.\n\nIt exists because the floor used to be recomputed per chunk from the\nworkflow's own clock, which made it a *moving* target: every hour the\ncrawl was delayed, the boundary advanced an hour with it. A connector\npaused mid-backfill (PRO-1762) makes that trivially reachable — pause for\nlonger than the crawl has left and it resumes, finds backfill_oldest\nalready at or past the recomputed floor, declares itself complete and\nclears the marker. The remaining history is never fetched and nothing\nreports it missing. Anchoring the boundary is what makes \"backfill 30\ndays\" mean 30 days from when it was asked for, however long the crawl\ntakes.\n\nEmpty on rows configured before this field existed; the workflow falls\nback to the old now-relative computation for those, so their behaviour is\nunchanged rather than silently altered by a deploy.", + "type": "string" + }, "backfill_next_chunk_at": { "description": "BackfillNextChunkAt is the RFC3339 time the next chunk becomes due. The\nbackfill workflow processes one chunk then sets this to now+interval and\nexits; the connector scheduler starts the next chunk once it passes.", "type": "string" @@ -81,6 +93,18 @@ }, "type": "object" }, + "page_acl_warning": { + "description": "PageACLWarning is the same signal for SOURCE-level failures inside this\nresource: individual pages whose own restrictions could not be resolved\nand were therefore opened (Confluence, PRO-1684).\n\nA SEPARATE field from ACLWarning on purpose. The two are written by\ndifferent steps at different points in a sync, and ACLWarning is CLEARED\nwhenever resource capture succeeds. Sharing one field would let a healthy\nspace wipe a live page warning every cycle, leaving a window in which the\ndashboard reports no problems while pages are still open — a false\nall-clear on an access-control surface, which is worse than no surface.", + "type": "string" + }, + "page_acl_warning_at": { + "description": "PageACLWarningAt is when PageACLWarning last CHANGED (RFC3339).", + "type": "string" + }, + "page_acl_warning_run": { + "description": "PageACLWarningRun is the drain run that last observed a page failing open\nhere. It is what makes the warning self-clearing: the drain settles each\nresource at the END of a cycle, and a stored run that is not the current\none means that whole cycle passed with nothing failing, so the warning is\nwithdrawn. Durable on purpose — the alternative was remembering it in the\nworker, which a restart loses and which has no moment that means \"all\npages have now been judged\".", + "type": "string" + }, "provider_cursor": { "description": "Bookmark of the last synced position. Non-empty value confirms the first sync has run.", "example": "1699999999.000100", @@ -221,51 +245,55 @@ ] }, "feedback.SubmitRequest": { - "anyOf": [ - { - "properties": { - "feedback": { - "minLength": 1, - "pattern": "\\S" - } - }, - "required": [ - "feedback" - ] - }, + "allOf": [ { - "properties": { - "ground_truth": { - "anyOf": [ - { - "properties": { - "answer": { - "minLength": 1, - "pattern": "\\S" - } - }, - "required": [ - "answer" - ] - }, - { - "properties": { - "source_ids": { - "contains": { - "minLength": 1, - "pattern": "\\S" - } + "anyOf": [ + { + "patternProperties": { + "^feedback$": { + "minLength": 1, + "pattern": "\\S" + } + }, + "required": [ + "feedback" + ] + }, + { + "patternProperties": { + "^ground_truth$": { + "anyOf": [ + { + "properties": { + "answer": { + "minLength": 1, + "pattern": "\\S" + } + }, + "required": [ + "answer" + ] + }, + { + "properties": { + "source_ids": { + "contains": { + "minLength": 1, + "pattern": "\\S" + } + } + }, + "required": [ + "source_ids" + ] } - }, - "required": [ - "source_ids" ] } + }, + "required": [ + "ground_truth" ] } - }, - "required": [ - "ground_truth" ] } ], @@ -466,6 +494,16 @@ }, "type": "object" }, + "github_com_hydradb_hydradb-application_internal_platform_storagelayout.Layout": { + "description": "StorageLayout is the physical storage layout the database is created with,\nfrom the request's `type` field. \"split\" is the two-collection layout every\ndatabase uses, and the default. Fixed at creation and IMMUTABLE thereafter:\nthe layout decides how every entity id is hashed, so a database that changed\nits mind would orphan everything already stored.", + "enum": [ + "split" + ], + "type": "string", + "x-enum-varnames": [ + "LayoutSplit" + ] + }, "github_com_hydradb_hydradb-application_internal_service.MetadataEditResult": { "properties": { "acl_drift_recorded": { @@ -1074,40 +1112,6 @@ }, "type": "object" }, - "handler.Envelope-tenants_SubTenantDeleteResponse": { - "properties": { - "data": { - "$ref": "#/components/schemas/tenants.SubTenantDeleteResponse", - "example": { - "collection": "engineering", - "database": "acme_corp", - "message": "Collection deregistered. Background cleanup is in progress.", - "status": "deletion_scheduled" - } - }, - "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } - }, - "meta": { - "$ref": "#/components/schemas/handler.responseMeta", - "example": { - "latency_ms": 12.3, - "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d" - } - }, - "success": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, "handler.Envelope-feedback_SubmitResponse": { "properties": { "data": { @@ -1779,12 +1783,86 @@ }, "type": "object" }, + "handler.Envelope-search_EntityProfileView": { + "properties": { + "data": { + "$ref": "#/components/schemas/search.EntityProfileView", + "example": { + "entity_id": "entity_1a2b", + "entries": [ + { + "confidence": 0.92 + } + ], + "name": "general", + "pending_importance": 1, + "version": 1 + } + }, + "error": { + "$ref": "#/components/schemas/handler.apiError", + "description": "Error message, empty string on success.", + "example": { + "code": "DATABASE_NOT_FOUND", + "message": "Database not found" + } + }, + "meta": { + "$ref": "#/components/schemas/handler.responseMeta", + "example": { + "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": { + "description": "Whether the request succeeded.", + "example": true, + "type": "boolean" + } + }, + "type": "object" + }, "handler.Envelope-search_V2RetrievalResult": { "properties": { "data": { - "$ref": "#/components/schemas/search.V2RetrievalResult", + "description": "The response body, in the shape the database answers with: the v2 body (chunks, graph_context, sources and related fields), or the four-key body (chunks, graph, forceful_relations, llm_prompt).", "example": { "additional_context": "The user is a senior engineer onboarding to the platform.", + "app_search_fusion": { + "stats": { + "app_chunks": 1, + "app_has_exact_ids": true, + "app_lane_empty_text": true, + "consensus": 1, + "exact_candidates": 1, + "exact_promoted": 1, + "limit": 1, + "normal_chunks": 1, + "normal_displaced": 1, + "tail_added": 1, + "tail_candidates": 1 + }, + "stats_by_pass": [ + { + "app_chunks": 1, + "app_has_exact_ids": true, + "app_lane_empty_text": true, + "consensus": 1, + "exact_candidates": 1, + "exact_promoted": 1, + "limit": 1, + "normal_chunks": 1, + "normal_displaced": 1, + "tail_added": 1, + "tail_candidates": 1 + } + ] + }, "chunks": [ { "additional_metadata": { @@ -1812,6 +1890,109 @@ "sub_tenant_id": "sub_tenant_4567" } ], + "code_search": { + "duration_ms": 0.5, + "repos": [ + { + "duration_ms": 0.5, + "error": "", + "status": "completed", + "truncated": true, + "unsigned": true + } + ], + "status": "completed" + }, + "forceful_relations": { + "declared": [ + { + "chunk": { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", + "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "collection": "team_docs", + "extra_context_ids": [ + "HydraEmbeddings123_2", + "HydraEmbeddings123_3" + ], + "id": "HydraDoc1234", + "layout": "text", + "metadata": { + "department": "finance", + "priority": 7 + }, + "relevancy_score": 0.87, + "source_last_updated_time": "2026-07-02T12:30:00Z", + "source_title": "Project Phoenix Overview", + "source_type": "file", + "source_upload_time": "2026-07-02T10:00:00Z", + "sub_tenant_id": "sub_tenant_4567" + } + } + ], + "inferred": [ + { + "chunk": { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", + "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "collection": "team_docs", + "extra_context_ids": [ + "HydraEmbeddings123_2", + "HydraEmbeddings123_3" + ], + "id": "HydraDoc1234", + "layout": "text", + "metadata": { + "department": "finance", + "priority": 7 + }, + "relevancy_score": 0.87, + "source_last_updated_time": "2026-07-02T12:30:00Z", + "source_title": "Project Phoenix Overview", + "source_type": "file", + "source_upload_time": "2026-07-02T10:00:00Z", + "sub_tenant_id": "sub_tenant_4567" + } + } + ] + }, + "graph": { + "paths": [ + { + "chunk_ids": [ + "HydraEmbeddings123_0", + "HydraEmbeddings123_1" + ], + "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "relevancy_score": 0.87, + "triplets": [ + { + "relation": { + "confidence": 0.92, + "predicate": "works_at" + }, + "source": { + "entity_id": "entity_1a2b", + "name": "Ada", + "type": "person" + }, + "target": { + "entity_id": "entity_3c4d", + "name": "Acme Corp", + "type": "organization" + } + } + ] + } + ] + }, "graph_context": { "chunk_id_to_group_ids": { "HydraEmbeddings123_0": [ @@ -1877,6 +2058,36 @@ } ] }, + "profile_context": { + "entity_id": "entity_1a2b", + "entries": [ + { + "confidence": 0.92 + } + ], + "name": "general", + "version": 1 + }, + "profile_filter": { + "applied": true, + "degraded": true, + "entity_id": "entity_1a2b", + "found": true, + "selected_entries": 1, + "version": 1 + }, + "profiles": [ + { + "entity_id": "entity_1a2b", + "entries": [ + { + "confidence": 0.92 + } + ], + "name": "general", + "version": 1 + } + ], "source_facts": [ { "app_kind": "slack", @@ -1959,7 +2170,15 @@ "promoted": 1, "truncated": true } - } + }, + "oneOf": [ + { + "$ref": "#/components/schemas/search.V2RetrievalResult" + }, + { + "$ref": "#/components/schemas/search.QueryResult" + } + ] }, "error": { "$ref": "#/components/schemas/handler.apiError", @@ -2052,6 +2271,48 @@ }, "message": "Success", "org_id": "org_1a2b3c", + "tenant_id": "tenant_1234", + "type": "split" + } + }, + "error": { + "$ref": "#/components/schemas/handler.apiError", + "description": "Error message, empty string on success.", + "example": { + "code": "DATABASE_NOT_FOUND", + "message": "Database not found" + } + }, + "meta": { + "$ref": "#/components/schemas/handler.responseMeta", + "example": { + "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": { + "description": "Whether the request succeeded.", + "example": true, + "type": "boolean" + } + }, + "type": "object" + }, + "handler.Envelope-tenants_SubTenantDeleteResponse": { + "properties": { + "data": { + "$ref": "#/components/schemas/tenants.SubTenantDeleteResponse", + "example": { + "collection": "team_docs", + "database": "acme_corp", + "message": "Success", + "status": "completed", + "sub_tenant_id": "sub_tenant_4567", "tenant_id": "tenant_1234" } }, @@ -2214,6 +2475,12 @@ "acme_corp", "research_kb" ], + "details": [ + { + "database": "acme_corp", + "type": "split" + } + ], "failed_databases": [ { "database": "acme_corp", @@ -2310,19 +2577,15 @@ }, "type": "object" }, - "handler.Envelope-tenants_TenantStatsResponse": { + "handler.Envelope-tenants_TenantRenameResponse": { "properties": { "data": { - "$ref": "#/components/schemas/tenants.TenantStatsResponse", + "$ref": "#/components/schemas/tenants.TenantRenameResponse", "example": { + "connector_reassignment": "complete", "database": "acme_corp", - "knowledge_collection": { - "row_count": 1280 - }, - "memory_collection": { - "row_count": 1280 - }, "message": "Success", + "status": "completed", "tenant_id": "tenant_1234" } }, @@ -2354,19 +2617,63 @@ }, "type": "object" }, - "handler.Envelope-webhooks_DeliveryItem": { + "handler.Envelope-tenants_TenantStatsResponse": { "properties": { "data": { - "$ref": "#/components/schemas/webhooks.DeliveryItem", + "$ref": "#/components/schemas/tenants.TenantStatsResponse", "example": { - "attempts": 1, - "created_at": "2026-07-02T10:00:00Z", - "delivery_id": "dlv_9f8e7d6c", - "doc_id": "HydraDoc1234", - "error_code": "", - "error_message": "", - "event_type": "indexing.status_changed", - "indexing_status": "completed", + "database": "acme_corp", + "knowledge_collection": { + "row_count": 1280 + }, + "memory_collection": { + "row_count": 1280 + }, + "message": "Success", + "tenant_id": "tenant_1234" + } + }, + "error": { + "$ref": "#/components/schemas/handler.apiError", + "description": "Error message, empty string on success.", + "example": { + "code": "DATABASE_NOT_FOUND", + "message": "Database not found" + } + }, + "meta": { + "$ref": "#/components/schemas/handler.responseMeta", + "example": { + "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": { + "description": "Whether the request succeeded.", + "example": true, + "type": "boolean" + } + }, + "type": "object" + }, + "handler.Envelope-webhooks_DeliveryItem": { + "properties": { + "data": { + "$ref": "#/components/schemas/webhooks.DeliveryItem", + "example": { + "attempts": 1, + "created_at": "2026-07-02T10:00:00Z", + "delivery_id": "dlv_9f8e7d6c", + "doc_id": "HydraDoc1234", + "error_code": "", + "error_message": "", + "event_type": "indexing.status_changed", + "indexing_status": "completed", "status": "completed", "updated_at": "2026-07-02T10:00:05Z" } @@ -2896,6 +3203,14 @@ "example": 86400, "type": "integer" }, + "full_visibility_roles": { + "description": "FullVisibilityRoles names the HubSpot roles whose members can see every\nrecord (PRO-2036). Resolved to ids against the portal at configure time\nand stored on the account-wide resource row. A pointer so an omitted\nfield keeps the current setting while an explicit [] clears it.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, "lookback_days": { "description": "How far back the first sync fetches historical data. Only applies to the initial sync — subsequent syncs are incremental from the last cursor.", "example": 30, @@ -3073,6 +3388,18 @@ "example": "2026-07-02T18:00:00Z", "type": "string" }, + "paused": { + "description": "Paused marks a connector its owner deliberately stopped (PRO-1762). It\nparks next_sync_at as SyncBlocked does, but stays a separate field:\nblocking clears itself once the cause is fixed, whereas only an explicit\nresume lifts a pause. Resources keep their committed provider_cursor, so\nresuming continues from where each stream stopped.", + "example": true, + "type": "boolean" + }, + "paused_at": { + "type": "string" + }, + "paused_next_sync_at": { + "description": "PausedNextSyncAt preserves the schedule the pause displaced. Resume makes\nthe connector due immediately, so this is read back only to recover from\na pause applied by mistake.", + "type": "string" + }, "provider": { "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", "example": "slack", @@ -3323,6 +3650,18 @@ "example": "2026-07-02T18:00:00Z", "type": "string" }, + "paused": { + "description": "Paused marks a connector its owner deliberately stopped (PRO-1762). It\nparks next_sync_at as SyncBlocked does, but stays a separate field:\nblocking clears itself once the cause is fixed, whereas only an explicit\nresume lifts a pause. Resources keep their committed provider_cursor, so\nresuming continues from where each stream stopped.", + "example": true, + "type": "boolean" + }, + "paused_at": { + "type": "string" + }, + "paused_next_sync_at": { + "description": "PausedNextSyncAt preserves the schedule the pause displaced. Resume makes\nthe connector due immediately, so this is read back only to recover from\na pause applied by mistake.", + "type": "string" + }, "provider": { "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", "example": "slack", @@ -3403,8 +3742,39 @@ }, "type": "object" }, + "handler.connectorLimitView": { + "description": "ConnectorLimit is present when the org has used its plan's connector\nallowance (Free: 3) and the caps mode enforces it: creating another\nconnector is refused with 402. Connectors already past the allowance\nkeep syncing; the limit applies to creating one. Absent otherwise, and\nsent with the health rollups only.", + "properties": { + "count": { + "description": "Total number of items returned.", + "example": 12, + "type": "integer" + }, + "limit": { + "example": 1, + "type": "integer" + }, + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" + }, + "plan": { + "type": "string" + } + }, + "type": "object" + }, "handler.connectorListResponse": { "properties": { + "connector_limit": { + "$ref": "#/components/schemas/handler.connectorLimitView", + "example": { + "count": 12, + "limit": 1, + "message": "Success" + } + }, "connectors": { "example": [ { @@ -3420,6 +3790,7 @@ "name": "general", "needs_reauth": true, "next_sync_at": "2026-07-02T18:00:00Z", + "paused": true, "provider": "slack", "provider_account_scope": "T12345ACME", "resources_pending_first_sync": 1, @@ -3442,14 +3813,45 @@ "additionalProperties": { "type": "string" }, - "description": "Health maps connector_id to its rollup (healthy | degraded | failed |\nchecking), present only when the caller asks for `?include=health`.\nA connector missing from the map has an unknown rollup — its resources\ncould not be read — which clients must not render as a failure.", + "description": "Health maps connector_id to its rollup (healthy | degraded | failed |\nchecking | capped), present only when the caller asks for\n`?include=health`. A connector missing from the map has an unknown\nrollup — its resources could not be read — which clients must not\nrender as a failure. `capped` is not a rollup of the connector: it is\noverlaid on a healthy, degraded or checking one when the org is at a\nplan cap, and PlanCap says which.", "type": "object" + }, + "plan_cap": { + "$ref": "#/components/schemas/handler.planCapView", + "example": { + "message": "Success" + } + } + }, + "type": "object" + }, + "handler.connectorPauseResponse": { + "properties": { + "connector_id": { + "description": "Connector this resource belongs to.", + "example": "conn_abc123", + "type": "string" + }, + "paused": { + "example": true, + "type": "boolean" + }, + "paused_at": { + "type": "string" } }, "type": "object" }, "handler.connectorResourceStatus": { "properties": { + "acl_warning": { + "description": "ACLWarning explains why permission capture could not read this\nresource. Capture fails OPEN, so while this is set the resource is\nreadable by EVERY caller regardless of the ACL they send. Deliberately\nnot folded into Status: the resource is syncing fine and its content is\ncurrent, so calling it failed would be wrong and would train people to\nignore a red badge. It is a separate signal because it needs a separate\nreaction (grant the missing permission, or set an access rule).", + "type": "string" + }, + "acl_warning_at": { + "description": "ACLWarningAt is when this warning was last CHANGED (RFC3339), not when\nthe failure was last observed. An unchanged warning is deliberately not\nrewritten every cycle, so treat this as \"open since\", not \"checked at\".", + "type": "string" + }, "action": { "description": "Action is what the user must do, when there is something they can do.", "type": "string" @@ -3477,6 +3879,14 @@ "example": "Success", "type": "string" }, + "page_acl_warning": { + "description": "PageACLWarning reports that individual PAGES inside this resource could\nnot have their own restrictions resolved and were opened to every caller.\nDistinct from ACLWarning above, which is about the resource itself: a\nresource can capture perfectly while pages inside it fail, and a healthy\nresource capture clears ACLWarning, so sharing one field would blank this\nevery cycle and report all-clear while pages are still open.", + "type": "string" + }, + "page_acl_warning_at": { + "description": "PageACLWarningAt is when PageACLWarning last CHANGED (RFC3339).", + "type": "string" + }, "resource_id": { "description": "Resource identifier from the Discover endpoint.", "example": "C0123456789", @@ -3548,7 +3958,7 @@ "type": "object" }, "handler.connectorStatusError": { - "description": "Error is the connector-level failure, set when Status is failed. Absent\nwhen the trouble is confined to individual resources — those carry their\nown messages below.", + "description": "Error is the connector-level failure: a rejected credential, a blocked\nconnector, or a latest sync cycle that failed as a whole. Absent when the\ntrouble is confined to individual resources — those carry their own\nmessages below.", "properties": { "action": { "description": "Action is what the user must do, when there is something they can do.", @@ -3605,6 +4015,12 @@ "example": "2026-07-02T18:00:00Z", "type": "string" }, + "plan_cap": { + "$ref": "#/components/schemas/handler.planCapView", + "example": { + "message": "Success" + } + }, "provider": { "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", "example": "slack", @@ -3631,7 +4047,7 @@ "uniqueItems": false }, "status": { - "description": "Status is the rollup: healthy | degraded | failed | checking. It is the\nworst of the credential state and every resource state.", + "description": "Status is the rollup: healthy | degraded | failed | checking, or capped\nwhen the org is at an enforced plan cap and the rollup was healthy,\ndegraded or checking (PlanCap then says which cap). It is the worst of\nthe credential state and every resource state.", "example": "completed", "type": "string" }, @@ -3926,6 +4342,64 @@ }, "type": "object" }, + "handler.instructionsResponse": { + "properties": { + "collections": { + "additionalProperties": { + "type": "string" + }, + "description": "Collections maps collection name to that collection's own instructions.", + "example": [ + "team_docs", + "engineering" + ], + "type": "object" + }, + "custom_instructions": { + "description": "CustomInstructions applies to every document ingested into the database.", + "type": "string" + }, + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", + "type": "string" + }, + "tenant_id": { + "deprecated": true, + "description": "TenantID mirrors Database as a deprecated alias, matching every other v2\ntenant response.", + "example": "acme_corp", + "type": "string", + "x-deprecated": "true" + } + }, + "type": "object" + }, + "handler.instructionsUpdateReq": { + "properties": { + "collections": { + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Collections merges per-collection instructions into the stored set: a\ncollection present with a value is set, a collection present with \"\" or\nnull is cleared, and a collection absent from the map is left untouched.\nMerge rather than replace so two people editing different collections\ncannot silently delete each other's work.", + "example": { + "engineering": null, + "team_docs": "Summarise decisions and who owns them." + }, + "type": "object" + }, + "custom_instructions": { + "description": "CustomInstructions sets the database-wide instructions. Send \"\" to clear.", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, "handler.metadataSchemaUpdateResponse": { "properties": { "added_fields": { @@ -3954,6 +4428,23 @@ }, "type": "object" }, + "handler.planCapView": { + "description": "PlanCap is present when the org is at a plan cap the caps mode\nenforces; every sync is skipped until the month resets or the plan\nchanges.", + "properties": { + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" + }, + "meter": { + "type": "string" + }, + "plan": { + "type": "string" + } + }, + "type": "object" + }, "handler.providerListResponse": { "properties": { "providers": { @@ -4256,63 +4747,254 @@ }, "type": "object" }, - "ingestion.SourceStatus": { - "description": "Status is the item's initial lifecycle state. Both modes share this\nvocabulary — memory mode reuses the same values.", - "enum": [ - "queued", - "processing", - "completed", - "failed" - ], - "type": "string", - "x-enum-varnames": [ - "SourceStatusQueued", - "SourceStatusProcessing", - "SourceStatusCompleted", - "SourceStatusFailed" - ] - }, - "ingestion.V2BatchProcessingStatus": { + "handler.vaultCredentialEntry": { "properties": { - "statuses": { - "description": "Per-source indexing status results.", - "example": [ - { - "error_code": "", - "error_message": "", - "id": "HydraDoc1234", - "indexing_status": "completed", - "message": "Source processed successfully.", - "success": true - } - ], + "collection": { + "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "example": "team_docs", + "type": "string" + }, + "connector_id": { + "description": "Connector this resource belongs to.", + "example": "conn_abc123", + "type": "string" + }, + "credential_id": { + "type": "string" + }, + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", + "type": "string" + }, + "fields": { "items": { - "$ref": "#/components/schemas/ingestion.V2ProcessingStatus" + "type": "string" }, "type": "array", "uniqueItems": false + }, + "label": { + "type": "string" + }, + "provider": { + "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", + "example": "slack", + "type": "string" } }, "type": "object" }, - "ingestion.V2IngestResponse": { + "handler.vaultCredentialListResponse": { "properties": { - "failed_count": { - "description": "Number of uploaded files that failed to queue.", - "example": 0, + "count": { + "description": "Total number of items returned.", + "example": 12, "type": "integer" }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "results": { - "description": "Per-item results.", - "example": [ - { - "error": "", - "filename": "policy.pdf", + "credentials": { + "description": "Provider-specific credentials (typically `{\"api_token\": \"...\"}` or `{\"access_token\": \"...\"}`).", + "example": { + "api_token": "xoxb-..." + }, + "items": { + "$ref": "#/components/schemas/handler.vaultCredentialEntry" + }, + "type": "array", + "uniqueItems": false + }, + "unavailable_count": { + "example": 1, + "type": "integer" + } + }, + "type": "object" + }, + "handler.vaultCredentialRevealReq": { + "properties": { + "field": { + "type": "string" + } + }, + "required": [ + "field" + ], + "type": "object" + }, + "handler.vaultCredentialRevealResponse": { + "properties": { + "credential_id": { + "type": "string" + }, + "field": { + "type": "string" + }, + "provider": { + "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", + "example": "slack", + "type": "string" + }, + "value": {} + }, + "type": "object" + }, + "handler.vaultCredentialUpdateReq": { + "properties": { + "credentials": { + "additionalProperties": {}, + "description": "Provider-specific credentials (typically `{\"api_token\": \"...\"}` or `{\"access_token\": \"...\"}`).", + "example": { + "api_token": "xoxb-..." + }, + "type": "object" + } + }, + "required": [ + "credentials" + ], + "type": "object" + }, + "handler.vaultCredentialUpdateResponse": { + "properties": { + "credential_id": { + "type": "string" + }, + "updated": { + "description": "Whether the source metadata was updated.", + "example": true, + "type": "boolean" + } + }, + "type": "object" + }, + "ingestion.GraphEntity": { + "properties": { + "identifier": { + "example": "Acme Corp", + "type": "string" + }, + "name": { + "description": "Human-readable label for this resource.", + "example": "general", + "type": "string" + }, + "namespace": { + "description": "Namespace grouping for the entity (e.g. `organization`, `person`).", + "example": "organization", + "type": "string" + }, + "type": { + "example": "knowledge", + "type": "string" + } + }, + "type": "object" + }, + "ingestion.GraphPayload": { + "properties": { + "entities": { + "additionalProperties": { + "$ref": "#/components/schemas/ingestion.GraphEntity" + }, + "type": "object" + }, + "relations": { + "example": [ + { + "context": "Ada joined Acme Corp in 2024 as a staff engineer.", + "temporal_details": "since 2024" + } + ], + "items": { + "$ref": "#/components/schemas/ingestion.GraphRelation" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object" + }, + "ingestion.GraphRelation": { + "properties": { + "context": { + "description": "Verbatim passage from the source that evidences the relationship.", + "example": "Ada joined Acme Corp in 2024 as a staff engineer.", + "type": "string" + }, + "predicate": { + "type": "string" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "temporal_details": { + "description": "Temporal context extracted alongside the relationship (e.g. `since 2024`). Serialized as null rather than omitted.", + "example": "since 2024", + "type": "string" + } + }, + "type": "object" + }, + "ingestion.SourceStatus": { + "description": "Status is the item's initial lifecycle state. Both modes share this\nvocabulary — memory mode reuses the same values.", + "enum": [ + "queued", + "processing", + "completed", + "failed" + ], + "type": "string", + "x-enum-varnames": [ + "SourceStatusQueued", + "SourceStatusProcessing", + "SourceStatusCompleted", + "SourceStatusFailed" + ] + }, + "ingestion.V2BatchProcessingStatus": { + "properties": { + "statuses": { + "description": "Per-source indexing status results.", + "example": [ + { + "error_code": "", + "error_message": "", + "id": "HydraDoc1234", + "indexing_status": "completed", + "message": "Source processed successfully.", + "success": true + } + ], + "items": { + "$ref": "#/components/schemas/ingestion.V2ProcessingStatus" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object" + }, + "ingestion.V2IngestResponse": { + "properties": { + "failed_count": { + "description": "Number of uploaded files that failed to queue.", + "example": 0, + "type": "integer" + }, + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" + }, + "results": { + "description": "Per-item results.", + "example": [ + { + "error": "", + "filename": "policy.pdf", "id": "HydraDoc1234", "infer": true, "relations_created": 5, @@ -4537,10 +5219,11 @@ "x-deprecated": "true" }, "type": { - "description": "Bucket to list: `knowledge` (default) or `memory`.", + "description": "Type names the corpus: knowledge (default) or memory.", "enum": [ "knowledge", - "memory" + "memory", + "all" ], "example": "knowledge", "type": "string" @@ -4898,68 +5581,709 @@ ], "type": "object" }, - "search.ChunkInspectResult": { + "memories.ConversationTurn": { "properties": { - "chunks": { - "example": [ - { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_id": "HydraDoc1234", - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } - ], + "content": { + "description": "Extracted text content of the source document.", + "example": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", + "type": "string" + }, + "name": { + "description": "Human-readable label for this resource.", + "example": "general", + "type": "string" + }, + "role": { + "type": "string" + } + }, + "type": "object" + }, + "memories.IngestItem": { + "properties": { + "acl": { + "description": "ACL is the item's access-control list (PRO-1684), the same contract as\nan app_knowledge item's `acl` on a split database: bare emails,\nuser_email:/group:/domain: principals, or the __public__/__private__\nsentinels. Omitted (nil) leaves the context unrestricted; an explicitly\nempty list stores __private__. Normalised here, all-or-nothing, so a\nmalformed principal is a 400 on the request rather than a silently\nmis-scoped context. Enforced by every read that takes `acl`.", "items": { - "$ref": "#/components/schemas/search.VectorStoreChunk" + "type": "string" }, "type": "array", "uniqueItems": false }, - "is_truncated": { - "description": "IsTruncated reports that the source has more chunks than the limit\nreturned, so the reader knows the text they see is a prefix of the\ndocument and not the whole of it.", - "example": false, - "type": "boolean" + "attributes": { + "additionalProperties": {}, + "type": "object" }, - "message": { - "description": "Human-readable result message.", - "example": "Success", + "content": { + "description": "Extracted text content of the source document.", + "example": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", "type": "string" }, - "missing_chunk_ids": { - "description": "MissingChunkIDs are ids the caller asked for that have no chunk row in\neither store. An expected, documented state rather than an error: on\nstaging 61% of one Slack collection's sources had graph relations but no\nchunk_data row at all (see attributedSourceID), and the vector store is\nnot guaranteed to still hold a re-ingested source's older chunk ids.\nAlways empty for a source-scoped read, which discovers ids rather than\nbeing handed them.", + "context_category": { + "description": "ContextCategory files this context under one of the three buckets\n(PRO-1618). Omitted or \"auto\" leaves it to HydraDB; naming a bucket pins\nit and inference will not overwrite it. See\ndomain/ingestion/context_category.go.", + "enum": [ + "auto", + "user_preference", + "business_knowledge", + "decision_trace" + ], + "type": "string" + }, + "context_id": { + "type": "string" + }, + "conversation": { + "description": "Conversation is the canonical name; `messages` is accepted as an alias.\nBoth are the shape a developer already builds for OpenAI or Anthropic.", + "example": [ + { + "content": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", + "name": "general" + } + ], "items": { - "type": "string" + "$ref": "#/components/schemas/memories.ConversationTurn" }, "type": "array", "uniqueItems": false }, - "success": { - "description": "Whether the request succeeded.", - "example": true, + "custom_attributes": { + "additionalProperties": {}, + "type": "object" + }, + "custom_instructions": { + "type": "string" + }, + "enrich": { + "example": true, + "type": "boolean" + }, + "forceful_relations": { + "$ref": "#/components/schemas/memories.ItemRelations", + "example": { + "ids": [ + "HydraDoc1234", + "HydraDoc4567" + ], + "source_ids": [ + "HydraDoc1234", + "HydraDoc4567" + ] + } + }, + "happened_at": { + "type": "string" + }, + "instructions": { + "description": "Instructions steer enrichment for this item. The request-level value is\nthe default when an item names none. `custom_instructions` is accepted\nas an alias for callers that still send the memories[] name.", + "type": "string" + }, + "is_markdown": { + "description": "IsMarkdown tells the pipeline the text is markdown, so it is chunked on\nstructure rather than as flat prose. The memories[] path has always\ncarried it; without it here a markdown sync has nowhere to say so.", + "example": true, + "type": "boolean" + }, + "messages": { + "example": [ + { + "content": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", + "name": "general" + } + ], + "items": { + "$ref": "#/components/schemas/memories.ConversationTurn" + }, + "type": "array", + "uniqueItems": false + }, + "relations": { + "$ref": "#/components/schemas/memories.ItemRelations", + "example": { + "ids": [ + "HydraDoc1234", + "HydraDoc4567" + ], + "source_ids": [ + "HydraDoc1234", + "HydraDoc4567" + ] + } + }, + "text": { + "description": "Text is the canonical name; `content` is accepted as an alias.", + "type": "string" + }, + "title": { + "description": "Title names the context. It becomes the context's document title, and it\nis what distinguishes two items whose text is identical: the document id\nis generated from the title, so without one they collide.", + "example": "Project Phoenix Overview", + "type": "string" + }, + "upsert": { + "description": "Upsert decides, for THIS item, whether an existing context with the same\ncontext_id is replaced. The request-level value is the default. This is\nwhat lets one call replace some contexts and append others.", + "example": "true", + "type": "boolean" + }, + "user_name": { + "description": "UserName is the speaker identity for a TEXT item. A conversation names\nits speaker per turn instead, and that stays authoritative: this field\nonly fills in when the turns supplied none. Empty ends up as \"User\",\nmatching the split path, so the pipeline is never handed a blank.", + "type": "string" + } + }, + "type": "object" + }, + "memories.ItemRelations": { + "description": "ForcefulRelations are the contexts the caller says this one relates to,\nby context_id. They are followed at query time (follow_forceful_relations)\nand returned as `relations[]`. Any item may declare them; on the split\nsurface only a knowledge item could. `relations` is accepted as an alias.", + "properties": { + "context_ids": { + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "ids": { + "example": [ + "HydraDoc1234", + "HydraDoc4567" + ], + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "properties": { + "additionalProperties": {}, + "type": "object" + }, + "source_ids": { + "example": [ + "HydraDoc1234", + "HydraDoc4567" + ], + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object" + }, + "memories.UnifiedIngestRequest": { + "properties": { + "collection": { + "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "example": "team_docs", + "type": "string" + }, + "context": { + "description": "Context is the list of contexts to ingest: the documented name. `items`\nand `contexts` are accepted as aliases because earlier drafts and the\nfirst client releases used them; they are folded onto Context before\nanything reads the request.", + "example": [ + { + "context_id": "q4_report", + "text": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", + "title": "Q4 Report" + } + ], + "items": { + "$ref": "#/components/schemas/memories.IngestItem" + }, + "type": "array", + "uniqueItems": false + }, + "contexts": { + "deprecated": true, + "items": { + "$ref": "#/components/schemas/memories.IngestItem" + }, + "type": "array", + "uniqueItems": false, + "x-deprecated": "true" + }, + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", + "type": "string" + }, + "enrich": { + "example": true, + "type": "boolean" + }, + "graph_payload": { + "additionalProperties": { + "$ref": "#/components/schemas/ingestion.GraphPayload" + }, + "description": "GraphPayload is the bring-your-own-graph map, keyed by context_id. Every\nkey must name an item in this request, so a typo cannot silently drop a\ngraph.", + "type": "object" + }, + "instructions": { + "type": "string" + }, + "items": { + "deprecated": true, + "items": { + "$ref": "#/components/schemas/memories.IngestItem" + }, + "type": "array", + "uniqueItems": false, + "x-deprecated": "true" + }, + "upsert": { + "description": "Upsert, Enrich and Instructions are the request-level defaults for the\nitem-level fields of the same name: true, true and \"\" when absent.", + "example": "true", + "type": "boolean" + } + }, + "type": "object" + }, + "search.AliasExpansionNote": { + "properties": { + "alias": { + "type": "string" + }, + "canonical": { + "type": "string" + } + }, + "type": "object" + }, + "search.AppSearchFusionDiagnostics": { + "description": "AppSearchFusion is the diagnostic block of the query_apps fusion\n(PRO-1882): per-chunk lane attribution and counts. Present only when\nquery_apps was on, the request was not ACL-scoped, and attributed chunks\nsurvived final filtering. Identifier maps cover only returned chunks.", + "properties": { + "app_recipes": { + "additionalProperties": { + "type": "string" + }, + "description": "AppRecipes maps the chunk_uuid of each final chunk the app lane returned to\nthe recipe that produced it (exact_id, recall, dated, bm25, broad, ...),\nincluding chunks the normal lane also had, so a consensus can be\nattributed to a recipe.", + "type": "object" + }, + "chunk_origins": { + "additionalProperties": { + "type": "string" + }, + "description": "ChunkOrigins maps every returned chunk_uuid to where the fusion placed it\nfrom: \"normal\" (normal lane only), \"both\" (both lanes, normal position\nkept), \"exact_id\" (promoted from the app lane's exact-identifier\nrecipe), \"app_tail\" (appended from the app lane).", + "type": "object" + }, + "stats": { + "$ref": "#/components/schemas/search.AppSearchFusionStats", + "description": "Counts for the first or only fusion pass before postprocessing, not final response counts or totals across alias alternatives. The entire diagnostic block is omitted for ACL-scoped requests.", + "example": { + "app_chunks": 1, + "app_has_exact_ids": true, + "app_lane_empty_text": true, + "consensus": 1, + "exact_candidates": 1, + "exact_promoted": 1, + "limit": 1, + "normal_chunks": 1, + "normal_displaced": 1, + "tail_added": 1, + "tail_candidates": 1 + } + }, + "stats_by_pass": { + "description": "StatsByPass preserves each independent fusion's accounting when results\ncombine multiple passes, in merge order (original before alternate when\nboth have diagnostics). Counts overlap; they are not unique totals.", + "example": [ + { + "app_chunks": 1, + "app_has_exact_ids": true, + "app_lane_empty_text": true, + "consensus": 1, + "exact_candidates": 1, + "exact_promoted": 1, + "limit": 1, + "normal_chunks": 1, + "normal_displaced": 1, + "tail_added": 1, + "tail_candidates": 1 + } + ], + "items": { + "$ref": "#/components/schemas/search.AppSearchFusionStats" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object" + }, + "search.AppSearchFusionStats": { + "description": "Stats describes the first (or only) fusion pass before postprocessing,\nnot final counts or a sum across alias alternatives/fan-out branches.", + "properties": { + "app_chunks": { + "description": "AppChunks is what the app lane returned.", + "example": 1, + "type": "integer" + }, + "app_has_exact_ids": { + "description": "AppHasExactIDs mirrors the app plan's exact-identifier marker.", + "example": true, + "type": "boolean" + }, + "app_lane_empty_text": { + "description": "AppLaneEmptyText is true when the app lane returned no chunks (sources\nor side context only).", + "example": true, + "type": "boolean" + }, + "consensus": { + "description": "Consensus counts app chunks the normal lane already had; they keep the\nnormal lane's position.", + "example": 1, + "type": "integer" + }, + "exact_candidates": { + "description": "ExactCandidates counts app chunks the exact-identifier recipe found;\nExactPromoted is how many of them were placed above the normal lane.", + "example": 1, + "type": "integer" + }, + "exact_promoted": { + "description": "Exact-identifier chunks placed above the normal lane.", + "example": 1, + "type": "integer" + }, + "limit": { + "description": "Limit is the final chunk limit the fusion applied.", + "example": 1, + "type": "integer" + }, + "normal_chunks": { + "description": "NormalChunks is what the normal lane returned.", + "example": 1, + "type": "integer" + }, + "normal_displaced": { + "description": "NormalDisplaced counts normal-lane chunks the promoted block and the\ntail pushed past the limit.", + "example": 1, + "type": "integer" + }, + "tail_added": { + "description": "App-only chunks appended within the tail budget.", + "example": 1, + "type": "integer" + }, + "tail_candidates": { + "description": "TailCandidates counts app-only chunks eligible for the tail; TailAdded\nis how many were appended within the tail budget.", + "example": 1, + "type": "integer" + } + }, + "type": "object" + }, + "search.ChunkInspectResult": { + "properties": { + "chunks": { + "example": [ + { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", + "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "extra_context_ids": [ + "HydraEmbeddings123_2", + "HydraEmbeddings123_3" + ], + "layout": "text", + "metadata": { + "department": "finance", + "priority": 7 + }, + "relevancy_score": 0.87, + "source_id": "HydraDoc1234", + "source_last_updated_time": "2026-07-02T12:30:00Z", + "source_title": "Project Phoenix Overview", + "source_type": "file", + "source_upload_time": "2026-07-02T10:00:00Z", + "sub_tenant_id": "sub_tenant_4567" + } + ], + "items": { + "$ref": "#/components/schemas/search.VectorStoreChunk" + }, + "type": "array", + "uniqueItems": false + }, + "is_truncated": { + "description": "IsTruncated reports that the source has more chunks than the limit\nreturned, so the reader knows the text they see is a prefix of the\ndocument and not the whole of it.", + "example": false, + "type": "boolean" + }, + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" + }, + "missing_chunk_ids": { + "description": "MissingChunkIDs are ids the caller asked for that have no chunk row in\neither store. An expected, documented state rather than an error: on\nstaging 61% of one Slack collection's sources had graph relations but no\nchunk_data row at all (see attributedSourceID), and the vector store is\nnot guaranteed to still hold a re-ingested source's older chunk ids.\nAlways empty for a source-scoped read, which discovers ids rather than\nbeing handed them.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "success": { + "description": "Whether the request succeeded.", + "example": true, + "type": "boolean" + } + }, + "type": "object" + }, + "search.CodeSearchRepoResult": { + "properties": { + "answer": { + "type": "string" + }, + "duration_ms": { + "example": 0.5, + "type": "number" + }, + "error": { + "description": "Error message, empty string on success.", + "example": "", + "type": "string" + }, + "repo": { + "type": "string" + }, + "status": { + "description": "Current lifecycle or processing state.", + "example": "completed", + "type": "string" + }, + "truncated": { + "example": true, + "type": "boolean" + }, + "unsigned": { + "example": true, "type": "boolean" } }, "type": "object" }, + "search.CodeSearchResult": { + "description": "CodeSearch is the repository code-search branch's answer, when routed.", + "properties": { + "decided_by": { + "description": "DecidedBy names the signal that routed the query: \"request\" (caller\nforced it), \"planner\" (is_code_query) or \"stage2\" (embedding router).", + "type": "string" + }, + "duration_ms": { + "description": "DurationMS is the wall time the branch took.", + "example": 0.5, + "type": "number" + }, + "reason": { + "description": "Reason explains a non-ok status in one sentence.", + "type": "string" + }, + "repos": { + "description": "Repos lists each repository searched with its own status and answer.", + "example": [ + { + "duration_ms": 0.5, + "error": "", + "status": "completed", + "truncated": true, + "unsigned": true + } + ], + "items": { + "$ref": "#/components/schemas/search.CodeSearchRepoResult" + }, + "type": "array", + "uniqueItems": false + }, + "status": { + "description": "Status is \"ok\" when at least one repository answered, \"not_found\" when\nnone had an archive, \"error\"/\"timeout\" when the branch failed, or\n\"skipped\" with a Reason when it was not attempted (no repositories\nconnected, caller opted out).", + "example": "completed", + "type": "string" + } + }, + "type": "object" + }, + "search.EntityProfileView": { + "properties": { + "compiled_at": { + "type": "string" + }, + "entity_id": { + "description": "Unique identifier for this entity in the graph.", + "example": "entity_1a2b", + "type": "string" + }, + "entity_type": { + "type": "string" + }, + "entries": { + "example": [ + { + "confidence": 0.92 + } + ], + "items": { + "$ref": "#/components/schemas/search.ProfileEntry" + }, + "type": "array", + "uniqueItems": false + }, + "headline": { + "type": "string" + }, + "name": { + "description": "Human-readable label for this resource.", + "example": "general", + "type": "string" + }, + "pending_importance": { + "example": 1, + "type": "integer" + }, + "perspective": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "summary_cites": { + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "unknown": { + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "version": { + "example": 1, + "type": "integer" + } + }, + "type": "object" + }, + "search.ForcefulRelationEntry": { + "properties": { + "chunk": { + "$ref": "#/components/schemas/search.V2Chunk", + "example": { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", + "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "collection": "team_docs", + "extra_context_ids": [ + "HydraEmbeddings123_2", + "HydraEmbeddings123_3" + ], + "id": "HydraDoc1234", + "layout": "text", + "metadata": { + "department": "finance", + "priority": 7 + }, + "relevancy_score": 0.87, + "source_last_updated_time": "2026-07-02T12:30:00Z", + "source_title": "Project Phoenix Overview", + "source_type": "file", + "source_upload_time": "2026-07-02T10:00:00Z", + "sub_tenant_id": "sub_tenant_4567" + } + }, + "via": { + "$ref": "#/components/schemas/search.RelationVia" + } + }, + "type": "object" + }, + "search.ForcefulRelationsBucket": { + "description": "ForcefulRelations is the caller-declared relation bucket, carrying the\nfrom-\u003eto edge that additional_context discards when it flattens these\ninto a chunk-uuid map. Always present, so a caller can read it\nunconditionally.", + "properties": { + "declared": { + "example": [ + { + "chunk": { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", + "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "collection": "team_docs", + "extra_context_ids": [ + "HydraEmbeddings123_2", + "HydraEmbeddings123_3" + ], + "id": "HydraDoc1234", + "layout": "text", + "metadata": { + "department": "finance", + "priority": 7 + }, + "relevancy_score": 0.87, + "source_last_updated_time": "2026-07-02T12:30:00Z", + "source_title": "Project Phoenix Overview", + "source_type": "file", + "source_upload_time": "2026-07-02T10:00:00Z", + "sub_tenant_id": "sub_tenant_4567" + } + } + ], + "items": { + "$ref": "#/components/schemas/search.ForcefulRelationEntry" + }, + "type": "array", + "uniqueItems": false + }, + "inferred": { + "example": [ + { + "chunk": { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", + "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "collection": "team_docs", + "extra_context_ids": [ + "HydraEmbeddings123_2", + "HydraEmbeddings123_3" + ], + "id": "HydraDoc1234", + "layout": "text", + "metadata": { + "department": "finance", + "priority": 7 + }, + "relevancy_score": 0.87, + "source_last_updated_time": "2026-07-02T12:30:00Z", + "source_title": "Project Phoenix Overview", + "source_type": "file", + "source_upload_time": "2026-07-02T10:00:00Z", + "sub_tenant_id": "sub_tenant_4567" + } + } + ], + "items": { + "$ref": "#/components/schemas/search.ForcefulRelationEntry" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object" + }, "search.GraphContext": { + "deprecated": true, "description": "GraphContext is omitted entirely when graph_context is disabled on the\nrequest (pointer + omitempty), so the response carries no graph slice\ninstead of an empty-but-present object.", "properties": { "chunk_id_to_group_ids": { @@ -5009,22 +6333,116 @@ } ], "items": { - "$ref": "#/components/schemas/search.ScoredPathResponse" + "$ref": "#/components/schemas/search.ScoredPathResponse" + }, + "type": "array", + "uniqueItems": false + }, + "query_paths": { + "description": "Scored relation paths ranked by relevance to the query.", + "example": [ + { + "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "group_id": "grp_1234", + "relevancy_score": 0.87, + "source_chunk_ids": [ + "HydraEmbeddings123_0", + "HydraEmbeddings123_1" + ], + "triplets": [ + { + "relation": { + "confidence": 0.92, + "predicate": "works_at" + }, + "source": { + "entity_id": "entity_1a2b", + "name": "Ada", + "type": "person" + }, + "target": { + "entity_id": "entity_3c4d", + "name": "Acme Corp", + "type": "organization" + } + } + ] + } + ], + "items": { + "$ref": "#/components/schemas/search.ScoredPathResponse" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object", + "x-deprecated": "true" + }, + "search.GraphPath": { + "properties": { + "chunk_ids": { + "example": [ + "HydraEmbeddings123_0", + "HydraEmbeddings123_1" + ], + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "combined_context": { + "description": "Merged text from all chunk passages in this relation path.", + "example": "Acme Corp deploys HydraDB in production for context retrieval.", + "type": "string" + }, + "relevancy_score": { + "description": "Relevance score for this item against the query.", + "example": 0.87, + "type": "number" + }, + "triplets": { + "description": "Knowledge-graph triplets that make up this relation path.", + "example": [ + { + "relation": { + "confidence": 0.92, + "predicate": "works_at" + }, + "source": { + "entity_id": "entity_1a2b", + "name": "Ada", + "type": "person" + }, + "target": { + "entity_id": "entity_3c4d", + "name": "Acme Corp", + "type": "organization" + } + } + ], + "items": { + "$ref": "#/components/schemas/search.PathTriplet" }, "type": "array", "uniqueItems": false - }, - "query_paths": { - "description": "Scored relation paths ranked by relevance to the query.", + } + }, + "type": "object" + }, + "search.GraphPlane": { + "description": "Graph is the consolidated graph plane: query_paths and\nchunk_relations consolidated into one ordered paths[] list, with each\npath carrying the chunk ids it supports so the caller no longer joins\nagainst chunk_id_to_group_ids. Populated whenever graph_context is on;\ngraph_context stays populated beside it.", + "properties": { + "paths": { "example": [ { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "group_id": "grp_1234", - "relevancy_score": 0.87, - "source_chunk_ids": [ + "chunk_ids": [ "HydraEmbeddings123_0", "HydraEmbeddings123_1" ], + "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "relevancy_score": 0.87, "triplets": [ { "relation": { @@ -5046,7 +6464,7 @@ } ], "items": { - "$ref": "#/components/schemas/search.ScoredPathResponse" + "$ref": "#/components/schemas/search.GraphPath" }, "type": "array", "uniqueItems": false @@ -5056,7 +6474,7 @@ }, "search.MetadataFilters": { "additionalProperties": {}, - "description": "Filters results by source metadata. Top-level keys target tenant metadata (for example department, priority, active, or tags). Nested additional_metadata keys target document metadata. Separate keys are ANDed. A scalar value is an exact match; an array means match ANY one of the listed values (OR) - there is no ALL/AND operator within a single key. Arrays are supported on VARCHAR fields only: an array passed for a declared field of any other type is rejected with 400 VALIDATION_ERROR. Size limits: each list may hold at most 500 values, and the whole metadata_filters object is capped at 64 KiB measured on its compact JSON encoding in UTF-8 bytes (keys and punctuation count). Exceeding either returns 400 naming the offending key or the actual byte count.", + "description": "DEPRECATED: use `attributes`, which is an operator language pushed into the vector search rather than bare equality applied after it. `metadata_filters` keeps working, and is still the only way to filter on per-context custom_attributes, which `attributes` does not cover yet. Filters results by context metadata. Top-level keys target tenant metadata (for example department, priority, active, or tags). Nested additional_metadata keys target document metadata. Separate keys are ANDed. Each top-level key accepts an operator object naming the comparison: {\"contains\": value} matches sources whose field holds that value (multi-value fields are stored comma-joined, so this matches one member); {\"contains_any\": [values]} matches sources holding ANY one of the listed values; {\"equals\": value} matches sources whose field is exactly that value. The bare forms remain supported and unchanged but are deprecated in favour of the operators, because the comparison they perform is inferred from the JSON shape rather than stated: a bare scalar behaves as equals, a bare array as contains_any, and a bare single-element array as contains. Operators apply to top-level keys only; inside additional_metadata use the bare scalar or array forms. An operator used inside additional_metadata is NOT rejected - it is read as an exact-match filter against a stored object, so on a normal field it matches nothing and the request returns 200 with an empty result rather than an error. A known operator given the wrong operand type, or several operators in one object, is rejected with 400 VALIDATION_ERROR rather than silently matching nothing. A MISSPELLED operator is not: {\"contian\": \"x\"} is indistinguishable from a filter for a stored object with that key, so it is left alone and matches nothing. An object whose keys are not operator names is likewise treated as an exact-match filter against a stored object, unchanged. RESERVED NAMES: contains, contains_any and equals are reserved as the keys of a top-level filter object, so an object built only from them is read as an operator and is no longer available for exact object matching -- {\"f\": {\"contains\": \"x\"}} is read as the operator, and an object whose keys are ALL operator names is rejected with 400. A caller matching such an object in a JSON-typed field must rename the nested key or the field. Mixing an operator name with any other key ({\"contains\": \"a\", \"other\": 1}) is unaffected and still exact-matches. There is no ALL/AND operator within a single key. contains, contains_any and arrays are supported on VARCHAR fields only: any of them passed for a declared field of another type is rejected with 400 VALIDATION_ERROR. equals works on every declared type, so {\"priority\": {\"equals\": 7}} is valid on an INT64 field. Size limits: each list may hold at most 500 values, and the whole metadata_filters object is capped at 64 KiB measured on its compact JSON encoding in UTF-8 bytes AFTER operator objects are reduced to their values, so {\"contains\": \"x\"} is measured as [\"x\"] and the operator keyword itself costs nothing. The cap bounds the cost of the resulting vector-store expression, which the operator spelling does not change. Field names and punctuation count. Exceeding either returns 400 naming the offending key or the actual byte count.", "example": { "active": true, "additional_metadata": { @@ -5077,57 +6495,358 @@ "and", "phrase" ], - "type": "string", - "x-enum-varnames": [ - "OperatorOr", - "OperatorAnd", - "OperatorPhrase" - ] + "type": "string", + "x-enum-varnames": [ + "OperatorOr", + "OperatorAnd", + "OperatorPhrase" + ] + }, + "search.PathTriplet": { + "properties": { + "relation": { + "additionalProperties": {}, + "description": "Relation properties including predicate and confidence score.", + "example": { + "confidence": 0.92, + "predicate": "works_at" + }, + "type": "object" + }, + "source": { + "additionalProperties": {}, + "description": "Source entity of the relationship.", + "example": { + "entity_id": "entity_1a2b", + "name": "Ada", + "type": "person" + }, + "type": "object" + }, + "target": { + "additionalProperties": {}, + "description": "Target entity of the relationship.", + "example": { + "entity_id": "entity_3c4d", + "name": "Acme Corp", + "type": "organization" + }, + "type": "object" + } + }, + "type": "object" + }, + "search.ProfileContext": { + "description": "ProfileContext/ProfileFilter surface the entity-profile block when the\nrequest named a profile_subject (PRO-1797); omitted otherwise.", + "properties": { + "entity_id": { + "description": "Unique identifier for this entity in the graph.", + "example": "entity_1a2b", + "type": "string" + }, + "entries": { + "example": [ + { + "confidence": 0.92 + } + ], + "items": { + "$ref": "#/components/schemas/search.ProfileEntry" + }, + "type": "array", + "uniqueItems": false + }, + "headline": { + "type": "string" + }, + "name": { + "description": "Human-readable label for this resource.", + "example": "general", + "type": "string" + }, + "perspective": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "version": { + "example": 1, + "type": "integer" + } + }, + "type": "object" + }, + "search.ProfileEntry": { + "properties": { + "confidence": { + "description": "Confidence score, from 0 to 1.", + "example": 0.92, + "type": "number" + }, + "facet": { + "type": "string" + }, + "since": { + "type": "string" + }, + "slot": { + "type": "string" + }, + "state": { + "description": "stated | observed | inferred | record", + "type": "string" + }, + "statement_keys": { + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "text": { + "type": "string" + } + }, + "type": "object" + }, + "search.ProfileFilterInfo": { + "properties": { + "applied": { + "example": true, + "type": "boolean" + }, + "degraded": { + "example": true, + "type": "boolean" + }, + "entity_id": { + "description": "Unique identifier for this entity in the graph.", + "example": "entity_1a2b", + "type": "string" + }, + "found": { + "example": true, + "type": "boolean" + }, + "selected_entries": { + "example": 1, + "type": "integer" + }, + "subject": { + "type": "string" + }, + "version": { + "example": 1, + "type": "integer" + } + }, + "type": "object" + }, + "search.QueryBy": { + "enum": [ + "hybrid", + "text" + ], + "type": "string", + "x-enum-varnames": [ + "QueryByHybrid", + "QueryByText" + ] + }, + "search.QueryChunk": { + "properties": { + "chunk_id": { + "description": "The chunk's id. Every graph hop names the chunk it was extracted from by this id.", + "type": "string" + }, + "content": { + "description": "The chunk's own text. Enrichment is not concatenated into it.", + "type": "string" + }, + "context_id": { + "description": "The id of the context (source) the chunk belongs to.", + "type": "string" + }, + "enrichment": { + "description": "What enrichment produced for the chunk, kept apart from content. Absent when nothing was produced.", + "type": "string" + }, + "enrichment_kind": { + "description": "The context_category the author declared at ingest (user_preference, business_knowledge or decision_trace). Never inferred. Absent when none was declared.", + "enum": [ + "user_preference", + "business_knowledge", + "decision_trace" + ], + "type": "string" + }, + "score": { + "description": "Relevance after reranking.", + "type": "number" + }, + "temporal": { + "description": "Dated facts extracted from the chunk. Present only when the query engaged temporal reasoning.", + "items": { + "$ref": "#/components/schemas/search.QueryChunkTemporal" + }, + "type": "array" + } + }, + "required": [ + "chunk_id", + "context_id", + "score", + "content" + ], + "type": "object" + }, + "search.QueryChunkTemporal": { + "properties": { + "content": { + "description": "The fact as a sentence with its dates embedded.", + "type": "string" + }, + "end_date": { + "description": "End of the fact's window, YYYY-MM-DD, or null.", + "type": [ + "string", + "null" + ] + }, + "start_date": { + "description": "Start of the fact's window, YYYY-MM-DD, or null.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "content", + "start_date", + "end_date" + ], + "type": "object" + }, + "search.QueryForcefulRelation": { + "properties": { + "chunk": { + "$ref": "#/components/schemas/search.QueryChunk" + }, + "via": { + "$ref": "#/components/schemas/search.RelationVia", + "description": "The declared edge that pulled the chunk in: from is the context that declared it, to is the chunk's own context." + } + }, + "required": [ + "via", + "chunk" + ], + "type": "object" + }, + "search.QueryGraphEdge": { + "properties": { + "chunk_id": { + "description": "The chunk the relation was extracted from. For a chunk_relation path this is the returned chunk the path hangs under.", + "type": "string" + }, + "context": { + "description": "The sentence the relation was extracted from.", + "type": "string" + }, + "predicate": { + "description": "The relation between the two entities.", + "type": "string" + }, + "relationship_id": { + "description": "The relation's stable id.", + "type": "string" + }, + "temporal_details": { + "description": "When the relation held, as extraction phrased it.", + "type": "string" + }, + "timestamp": { + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "predicate", + "context", + "relationship_id", + "chunk_id" + ], + "type": "object" + }, + "search.QueryGraphEntity": { + "properties": { + "entity_id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "entity_id", + "name" + ], + "type": "object" + }, + "search.QueryGraphPath": { + "properties": { + "origin": { + "description": "Which lane found the path: query_path (grown from the entities in the query) or chunk_relation (the neighbourhood of a returned chunk).", + "enum": [ + "query_path", + "chunk_relation" + ], + "type": "string" + }, + "path_summary": { + "description": "The path narrated as one sentence.", + "type": "string" + }, + "triplets": { + "description": "The path's hops, in order.", + "items": { + "$ref": "#/components/schemas/search.QueryGraphTriplet" + }, + "type": "array" + } + }, + "required": [ + "origin", + "triplets", + "path_summary" + ], + "type": "object" }, - "search.PathTriplet": { + "search.QueryGraphTriplet": { "properties": { "relation": { - "additionalProperties": {}, - "description": "Relation properties including predicate and confidence score.", - "example": { - "confidence": 0.92, - "predicate": "works_at" - }, - "type": "object" + "$ref": "#/components/schemas/search.QueryGraphEdge" }, "source": { - "additionalProperties": {}, - "description": "Source entity of the relationship.", - "example": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "type": "object" + "$ref": "#/components/schemas/search.QueryGraphEntity" }, "target": { - "additionalProperties": {}, - "description": "Target entity of the relationship.", - "example": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - }, - "type": "object" + "$ref": "#/components/schemas/search.QueryGraphEntity" } }, - "type": "object" - }, - "search.QueryBy": { - "enum": [ - "hybrid", - "text" + "required": [ + "source", + "relation", + "target" ], - "type": "string", - "x-enum-varnames": [ - "QueryByHybrid", - "QueryByText" - ] + "type": "object" }, "search.QueryRequest": { "properties": { @@ -5147,6 +6866,16 @@ "alpha": { "description": "Weighting balance between dense and sparse retrieval in hybrid mode. `\"auto\"` lets HydraDB choose; a number from 0 (full BM25) to 1 (full dense) sets it explicitly." }, + "attributes": { + "additionalProperties": {}, + "description": "Attributes is the go-forward metadata filter: a MongoDB-like operator query\n($eq/$ne/$gt/$gte/$lt/$lte/$in/$nin/$and/$or/$not/$exists) over the\ndatabase attributes, translated to a safe Milvus scalar pre-filter by\nBuildAttributesFilterExpr (PRO-1618). It composes (AND) with the\ndeprecated metadata_filters while both exist. Field names are allowlisted\nand values escaped, so it is injection-safe.\n\nIt is applied everywhere metadata_filters is, and nowhere else: the\nchunks a query returns, the additional context and forceful-relation\nchunks (the fail-closed post-filter net in the service), and the graph\npaths, which the graph lane prunes by resolving every source a path\ncites and dropping the paths that touch one failing the predicate\n(disallowedGraphSources). Product decision 2026-09-04: `attributes`\nbehaves like `metadata_filters` on every part of the response.", + "type": "object" + }, + "code_search": { + "description": "CodeSearch forces the repository code-search branch on (true) or off\n(false) for this query, overriding the classifier. Nil = let the\nclassifier decide. Only meaningful where the branch is enabled.", + "example": true, + "type": "boolean" + }, "collection": { "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", "example": "team_docs", @@ -5193,6 +6922,11 @@ "example": "acme_corp", "type": "string" }, + "follow_forceful_relations": { + "description": "Whether to follow the relations the author declared at ingest (forceful_relations) and return the related contexts. Defaults to true when omitted.", + "example": true, + "type": "boolean" + }, "graph_context": { "description": "Whether to include graph context in the response. Defaults to true for /query when omitted.", "example": true, @@ -5226,7 +6960,9 @@ "type": "integer" }, "metadata_filters": { - "$ref": "#/components/schemas/search.MetadataFilters" + "$ref": "#/components/schemas/search.MetadataFilters", + "deprecated": true, + "x-deprecated": true }, "mode": { "$ref": "#/components/schemas/search.RecallMode", @@ -5241,13 +6977,24 @@ "$ref": "#/components/schemas/search.Operator", "example": "and" }, + "profile_entity_type": { + "description": "ProfileEntityType/ProfileNamespace refine the subject's graph identity;\ndefaults (\"PERSON\"/\"users\") cover the common case of a person subject.", + "type": "string" + }, + "profile_namespace": { + "type": "string" + }, + "profile_subject": { + "description": "ProfileSubject names the entity whose compiled profile should ride the\nresponse as profile_context/profile_filter (PRO-1797). Payload-only:\nchunk ranking is never altered. Omitted = no profile block. Dark until\nthe repo-level ENTITY_PROFILE_CONTEXT_ENABLED flag is on.", + "type": "string" + }, "query": { "description": "Natural-language search query.", "example": "Which mode does the user prefer?", "type": "string" }, "query_apps": { - "description": "Whether to include app-aware knowledge retrieval. Applies to knowledge hybrid queries.", + "description": "Whether to include app-aware knowledge retrieval. Applies to knowledge hybrid queries. Defaults to true when omitted; pass false to search files only.", "example": true, "type": "boolean" }, @@ -5257,12 +7004,14 @@ "example": "hybrid" }, "query_forceful_relations": { - "description": "Whether to force relation expansion for graph-aware query retrieval. Defaults to true when omitted.", + "deprecated": true, + "description": "Deprecated alias for follow_forceful_relations. Ignored when follow_forceful_relations is sent.", "example": true, - "type": "boolean" + "type": "boolean", + "x-deprecated": "true" }, "recency_bias": { - "description": "Recency boost applied to ranking. 0 disables it; higher values favour more recent sources.", + "description": "Recency boost applied to ranking (0.0-1.0). Omit it to get the always-on default baseline of 0.40 (a bounded \u003c=40% swing on normalized relevance — it reorders within a relevance gap of up to 0.40 but never buries a more strongly relevant result); send 0 to disable recency entirely; higher values favour more recent sources more strongly.", "example": 0.2, "type": "number" }, @@ -5334,11 +7083,56 @@ "type": "string", "x-deprecated": "true" }, + "titles": { + "description": "Optional exact document-title filter. Values are matched case-insensitively and ORed, resolved to source IDs, then the normal query pipeline runs within that source scope. When ids is also supplied, the two filters are intersected.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, "type": { "$ref": "#/components/schemas/search.SourceType", - "description": "Corpus to query: knowledge, memory, or all." + "description": "Corpus to query: knowledge (the default), memory, or all (both, merged)." + } + }, + "type": "object" + }, + "search.QueryResult": { + "description": "The four-key /query response body: chunks, graph, forceful_relations and llm_prompt, and nothing else.", + "properties": { + "chunks": { + "description": "Retrieved chunks, ranked. Each carries its own text and enrichment, and nothing about its source: POST /context/list with its context_id in `ids` returns the source's title, type, collection, timestamp and metadata.", + "items": { + "$ref": "#/components/schemas/search.QueryChunk" + }, + "type": "array" + }, + "forceful_relations": { + "description": "Chunks pulled in because the author declared forceful_relations at ingest. [] when none were declared or follow_forceful_relations was false.", + "items": { + "$ref": "#/components/schemas/search.QueryForcefulRelation" + }, + "type": "array" + }, + "graph": { + "description": "Graph paths, query paths first then chunk relations, deduplicated. [] when graph_context was false.", + "items": { + "$ref": "#/components/schemas/search.QueryGraphPath" + }, + "type": "array" + }, + "llm_prompt": { + "description": "The whole response rendered as markdown for a model call: numbered results with their relevance, forceful relations, related facts labelled P1..Pn in `graph` order and citing the results they came from, temporal facts and sources. It also carries what this body has no key for: a computed duration, source facts, entity profiles, the code-search answer, the aliases and references the query was expanded with, decision-trace evidence, and a note when a lookup failed or was truncated. Inject it verbatim.", + "type": "string" } }, + "required": [ + "chunks", + "graph", + "forceful_relations", + "llm_prompt" + ], "type": "object" }, "search.RecallMode": { @@ -5354,6 +7148,28 @@ "RecallModeAuto" ] }, + "search.RelationVia": { + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "type": "object" + }, + "search.ResolvedReference": { + "properties": { + "expression": { + "type": "string" + }, + "resolved_to": { + "type": "string" + } + }, + "type": "object" + }, "search.ScoredPathResponse": { "properties": { "combined_context": { @@ -5866,9 +7682,53 @@ "additionalProperties": { "$ref": "#/components/schemas/search.V2Chunk" }, - "description": "Map of chunk ID to chunk content for sources declared as related by the author (query_forceful_relations).", + "deprecated": true, + "description": "deprecated: use forceful_relations", "example": "The user is a senior engineer onboarding to the platform.", - "type": "object" + "type": "object", + "x-deprecated": "true" + }, + "alias_expansions": { + "description": "AliasExpansions is the alias layer's honesty stamp (V0): which nickname\nwas expanded to which canonical name for this answer.", + "items": { + "$ref": "#/components/schemas/search.AliasExpansionNote" + }, + "type": "array", + "uniqueItems": false + }, + "app_search_fusion": { + "$ref": "#/components/schemas/search.AppSearchFusionDiagnostics", + "description": "App-search fusion diagnostics for unscoped requests: final returned chunk attribution and pre-postprocessing fusion counts. Omitted for ACL-scoped requests and when no attributed chunks remain.", + "example": { + "stats": { + "app_chunks": 1, + "app_has_exact_ids": true, + "app_lane_empty_text": true, + "consensus": 1, + "exact_candidates": 1, + "exact_promoted": 1, + "limit": 1, + "normal_chunks": 1, + "normal_displaced": 1, + "tail_added": 1, + "tail_candidates": 1 + }, + "stats_by_pass": [ + { + "app_chunks": 1, + "app_has_exact_ids": true, + "app_lane_empty_text": true, + "consensus": 1, + "exact_candidates": 1, + "exact_promoted": 1, + "limit": 1, + "normal_chunks": 1, + "normal_displaced": 1, + "tail_added": 1, + "tail_candidates": 1 + } + ] + } }, "chunks": { "description": "Retrieved and ranked chunks from the knowledge store or memories.", @@ -5905,6 +7765,118 @@ "type": "array", "uniqueItems": false }, + "code_search": { + "$ref": "#/components/schemas/search.CodeSearchResult", + "example": { + "duration_ms": 0.5, + "repos": [ + { + "duration_ms": 0.5, + "error": "", + "status": "completed", + "truncated": true, + "unsigned": true + } + ], + "status": "completed" + } + }, + "forceful_relations": { + "$ref": "#/components/schemas/search.ForcefulRelationsBucket", + "example": { + "declared": [ + { + "chunk": { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", + "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "collection": "team_docs", + "extra_context_ids": [ + "HydraEmbeddings123_2", + "HydraEmbeddings123_3" + ], + "id": "HydraDoc1234", + "layout": "text", + "metadata": { + "department": "finance", + "priority": 7 + }, + "relevancy_score": 0.87, + "source_last_updated_time": "2026-07-02T12:30:00Z", + "source_title": "Project Phoenix Overview", + "source_type": "file", + "source_upload_time": "2026-07-02T10:00:00Z", + "sub_tenant_id": "sub_tenant_4567" + } + } + ], + "inferred": [ + { + "chunk": { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", + "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "collection": "team_docs", + "extra_context_ids": [ + "HydraEmbeddings123_2", + "HydraEmbeddings123_3" + ], + "id": "HydraDoc1234", + "layout": "text", + "metadata": { + "department": "finance", + "priority": 7 + }, + "relevancy_score": 0.87, + "source_last_updated_time": "2026-07-02T12:30:00Z", + "source_title": "Project Phoenix Overview", + "source_type": "file", + "source_upload_time": "2026-07-02T10:00:00Z", + "sub_tenant_id": "sub_tenant_4567" + } + } + ] + } + }, + "graph": { + "$ref": "#/components/schemas/search.GraphPlane", + "example": { + "paths": [ + { + "chunk_ids": [ + "HydraEmbeddings123_0", + "HydraEmbeddings123_1" + ], + "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "relevancy_score": 0.87, + "triplets": [ + { + "relation": { + "confidence": 0.92, + "predicate": "works_at" + }, + "source": { + "entity_id": "entity_1a2b", + "name": "Ada", + "type": "person" + }, + "target": { + "entity_id": "entity_3c4d", + "name": "Acme Corp", + "type": "organization" + } + } + ] + } + ] + } + }, "graph_context": { "$ref": "#/components/schemas/search.GraphContext", "example": { @@ -5973,6 +7945,58 @@ ] } }, + "profile_context": { + "$ref": "#/components/schemas/search.ProfileContext", + "example": { + "entity_id": "entity_1a2b", + "entries": [ + { + "confidence": 0.92 + } + ], + "name": "general", + "version": 1 + } + }, + "profile_filter": { + "$ref": "#/components/schemas/search.ProfileFilterInfo", + "example": { + "applied": true, + "degraded": true, + "entity_id": "entity_1a2b", + "found": true, + "selected_entries": 1, + "version": 1 + } + }, + "profiles": { + "description": "Profiles are the auto-selected profiles for the query's graph-resolved\nentities (PRO-1797); additive to ProfileContext, omitted when none.", + "example": [ + { + "entity_id": "entity_1a2b", + "entries": [ + { + "confidence": 0.92 + } + ], + "name": "general", + "version": 1 + } + ], + "items": { + "$ref": "#/components/schemas/search.ProfileContext" + }, + "type": "array", + "uniqueItems": false + }, + "resolved_references": { + "description": "ResolvedReferences report description-based references the query resolved\nand expanded with (PRO-1797 Stage 2); omitted when none.", + "items": { + "$ref": "#/components/schemas/search.ResolvedReference" + }, + "type": "array", + "uniqueItems": false + }, "source_facts": { "description": "SourceFacts surface the matched app-native (edge_source) facts when\nsource_reasoning was active; omitted otherwise (PRO-1602).", "example": [ @@ -6274,10 +8298,11 @@ "x-deprecated": "true" }, "type": { - "description": "Bucket to delete from: `knowledge` (default) or `memory`.", + "description": "Type names the corpus: knowledge (default) or memory.", "enum": [ "knowledge", - "memory" + "memory", + "all" ], "example": "knowledge", "type": "string" @@ -6330,6 +8355,24 @@ }, "type": "object" }, + "tenants.DatabaseDetail": { + "properties": { + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", + "type": "string" + }, + "type": { + "description": "Type is the storage layout the database was created with: \"split\" (a\nknowledge and a memory corpus, selected by `type` on every call).\nAbsent where the deployment does not expose the layout.", + "enum": [ + "split" + ], + "example": "split", + "type": "string" + } + }, + "type": "object" + }, "tenants.FailedTenant": { "properties": { "database": { @@ -6385,6 +8428,14 @@ "example": "tenant_1234", "type": "string", "x-deprecated": "true" + }, + "type": { + "description": "Type is the storage layout the database was created with; absent while the\ndatabase is deleting or unknown.", + "enum": [ + "split" + ], + "example": "split", + "type": "string" } }, "type": "object" @@ -6417,6 +8468,7 @@ "type": "object" }, "tenants.MilvusDataType": { + "description": "Declared type of a database metadata schema field. ARRAY appears in this enum because schemas persisted before it was rejected still read back and rebuild, but it CANNOT be declared on a new or evolved field: both the create and the update-metadata-schema endpoints answer 400 for it. For a field holding several values, declare VARCHAR and store the values comma-joined, then filter one member with the contains operator.", "enum": [ "BOOL", "INT8", @@ -6443,6 +8495,43 @@ "DataTypeArray" ] }, + "tenants.SubTenantDeleteResponse": { + "properties": { + "collection": { + "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "example": "team_docs", + "type": "string" + }, + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", + "type": "string" + }, + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" + }, + "status": { + "description": "Current lifecycle or processing state.", + "example": "completed", + "type": "string" + }, + "sub_tenant_id": { + "deprecated": true, + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + }, + "tenant_id": { + "deprecated": true, + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + } + }, + "type": "object" + }, "tenants.SubTenantIdsResponse": { "properties": { "collections": { @@ -6556,43 +8645,9 @@ "type": "array", "uniqueItems": false, "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.SubTenantDeleteResponse": { - "properties": { - "collection": { - "description": "Collection that was deleted. Formerly `sub_tenant_id`.", - "example": "engineering", - "type": "string" - }, - "database": { - "description": "Owning database. Formerly `tenant_id`.", - "example": "acme_corp", - "type": "string" - }, - "message": { - "description": "Human-readable result message.", - "example": "Collection deregistered. Background cleanup is in progress.", - "type": "string" - }, - "status": { - "description": "Current lifecycle or processing state.", - "example": "deletion_scheduled", - "type": "string" - }, - "sub_tenant_id": { - "deprecated": true, - "example": "engineering", - "type": "string", - "x-deprecated": "true" }, - "tenant_id": { - "deprecated": true, - "example": "acme_corp", - "type": "string", - "x-deprecated": "true" + "type": { + "$ref": "#/components/schemas/github_com_hydradb_hydradb-application_internal_platform_storagelayout.Layout" } }, "type": "object" @@ -6637,6 +8692,20 @@ "type": "array", "uniqueItems": false }, + "details": { + "description": "Details carries one entry per live database with its storage layout.", + "example": [ + { + "database": "acme_corp", + "type": "split" + } + ], + "items": { + "$ref": "#/components/schemas/tenants.DatabaseDetail" + }, + "type": "array", + "uniqueItems": false + }, "failed_databases": { "description": "Databases that failed provisioning, with error details.", "example": [ @@ -6747,6 +8816,57 @@ }, "type": "object" }, + "tenants.TenantRenameRequest": { + "properties": { + "new_name": { + "description": "NewName is the database's new caller-facing name. Same rules as a\ncreate-time name (ValidateTenantName).\n\nbinding:\"required\" is read by swag, so the published schema lists the\nfield as required and generated SDKs make it a mandatory argument; it is\ninert at runtime, because the handler decodes through ParseRenameRequest,\nwhich rejects a missing or blank new_name itself.", + "type": "string" + } + }, + "required": [ + "new_name" + ], + "type": "object" + }, + "tenants.TenantRenameResponse": { + "properties": { + "connector_reassignment": { + "description": "ConnectorReassignment reports how the rename's connector sweep ended:\n\"complete\" (every connector already targets the new name), \"queued\" (a\ndurable background reconciliation owns the remainder and retries until\ndrained), or \"failed\" (neither — contact support; the failure is also\nalerted on server-side). The rename itself has succeeded in all three\nstates.", + "example": "complete", + "type": "string" + }, + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", + "type": "string" + }, + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" + }, + "old_database": { + "type": "string" + }, + "old_tenant_id": { + "deprecated": true, + "type": "string", + "x-deprecated": "true" + }, + "status": { + "description": "Current lifecycle or processing state.", + "example": "completed", + "type": "string" + }, + "tenant_id": { + "deprecated": true, + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + } + }, + "type": "object" + }, "tenants.TenantStatsResponse": { "properties": { "database": { @@ -7816,6 +9936,16 @@ } }, "description": "Bad Gateway" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Service Unavailable" } }, "security": [ @@ -7831,6 +9961,76 @@ "x-fern-sdk-method-name": "discover" } }, + "/connectors/{id}/pause": { + "post": { + "description": "Stop scheduling syncs and backfills for a connector until it is resumed. A sync already running is allowed to finish. Cursors are preserved, so resuming continues from where each resource left off.", + "parameters": [ + { + "description": "Connector ID", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "HydraDoc1234", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.connectorPauseResponse" + } + } + }, + "description": "OK" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Conflict" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Pause a connector", + "tags": [ + "connectors" + ], + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "pause" + } + }, "/connectors/{id}/resources": { "get": { "description": "List the configured resources for a connector.", @@ -8138,9 +10338,79 @@ "x-fern-sdk-method-name": "update_resource_acl" } }, + "/connectors/{id}/resume": { + "post": { + "description": "Return a paused connector to the schedule and make it due immediately. Each resource continues from its committed cursor, so data created during the pause is collected on the next cycle rather than skipped.", + "parameters": [ + { + "description": "Connector ID", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "HydraDoc1234", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.connectorPauseResponse" + } + } + }, + "description": "OK" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Conflict" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Resume a paused connector", + "tags": [ + "connectors" + ], + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "resume" + } + }, "/connectors/{id}/status": { "get": { - "description": "Report whether a connector is working, in one call: a rollup status (healthy, degraded, failed, checking) plus per-resource detail. `degraded` means the connector is syncing but at least one configured resource is failing — the state that is otherwise invisible, because a connector whose resources partly fail still reports an idle sync status and no error.", + "description": "Report whether a connector is working, in one call: a rollup status (healthy, degraded, failed, checking) plus per-resource detail. `degraded` means the connector is scheduled but not fully working: at least one configured resource is failing, or the latest sync cycle failed after the resources reported (in which case `error` carries the failure and `retryable` says whether waiting can fix it). `failed` means only the user can fix it: a rejected credential, a blocked connector, or a terminal cycle failure.", "parameters": [ { "description": "Connector ID", @@ -8442,13 +10712,14 @@ "style": "form" }, { - "description": "Corpus type: 'knowledge' or 'memory'", + "description": "Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", "in": "query", "name": "type", "schema": { "enum": [ "knowledge", - "memory" + "memory", + "all" ], "type": "string" } @@ -8512,43 +10783,75 @@ }, "/context/ingest": { "post": { - "description": "Ingest knowledge documents or memories for a tenant.", + "description": "Ingest content for a database. `context` is the list of contexts to ingest (text or a conversation per item), the preferred shape; the deprecated `documents`, `app_knowledge` and `memories` fields are selected by `type`. `items` is a deprecated alias of `context`, accepted for SDK releases that still send it. The same `context` array may also be sent as an application/json body.", "requestBody": { "content": { "multipart/form-data": { "schema": { "properties": { "app_knowledge": { - "description": "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:\u003cemail\u003e`, a bare email, `group:\u003cprovider\u003e:\u003cid\u003e`, `domain:\u003cdomain\u003e`, 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.", + "deprecated": true, + "description": "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:\u003cemail\u003e`, a bare email, `group:\u003cprovider\u003e:\u003cid\u003e`, `domain:\u003cdomain\u003e`, 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. Items may also carry `evidence_kind`/`evidence_subject` provenance labels (see document_metadata); an unknown kind returns 400.", "title": "app_knowledge", - "type": "string" + "type": "string", + "x-deprecated": "true" }, "collection": { "title": "collection", "type": "string" }, + "context": { + "description": "JSON-encoded array of contexts to ingest -- text or a conversation per item. The same array may also be POSTed as an application/json body under `context`; that variant is not listed here so SDK generators emit this form, which carries every field.", + "title": "context", + "type": "string" + }, "database": { "title": "database", "type": "string" }, "document_metadata": { - "description": "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.", + "deprecated": true, + "description": "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. Each item may also carry evidence labels (`evidence_kind`: one of assertion, record, said, done, third_party, inferred; `evidence_subject`: a stable handle for who the evidence is about, e.g. `user:kiran@acme.com`) declaring the content's provenance for entity understanding; an unknown kind returns 400.", "title": "document_metadata", - "type": "string" + "type": "string", + "x-deprecated": "true" }, "documents": { - "format": "binary", + "deprecated": true, + "items": { + "format": "binary", + "type": "string" + }, "title": "documents", + "type": "array", + "x-deprecated": "true" + }, + "enrich": { + "default": "true", + "title": "enrich", "type": "string" }, "graph_payload": { "title": "graph_payload", "type": "string" }, + "instructions": { + "title": "instructions", + "type": "string" + }, + "items": { + "deprecated": true, + "description": "Deprecated alias for `context`, accepted so SDK releases that send `items` keep working. Send `context` instead; a request carrying both is rejected with 400.", + "title": "items", + "type": "string", + "x-deprecated": "true" + }, "memories": { - "description": "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.", + "deprecated": true, + "description": "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. Items may also carry `evidence_kind`/`evidence_subject` provenance labels (see document_metadata); an unknown kind returns 400.", "title": "memories", - "type": "string" + "type": "string", + "x-deprecated": "true" }, "sub_tenant_id": { "deprecated": true, @@ -8563,7 +10866,6 @@ "x-deprecated": "true" }, "type": { - "default": "knowledge", "enum": [ "knowledge", "memory" @@ -8584,7 +10886,7 @@ } } }, - "description": "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 \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | App-knowledge items as a JSON array (type=knowledge). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB, optional acl principal list (PRO-1684). | Memory items as a JSON array (type=memory). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Optional bring-your-own-graph payload as JSON", + "description": "JSON body alternative to this form: the same fields, with the list under `context`. | Corpus to write to: 'knowledge' (default) or 'memory'. 'all' is refused here: an ingest must name the one corpus it writes to. | Contexts as a JSON array; each carries `text` or `conversation` (role/content turns), optional `context_id`, `title`, `attributes`, `custom_attributes`, `happened_at`, `enrich`, `upsert`, `instructions`, `forceful_relations`, `context_category` (auto|user_preference|business_knowledge|decision_trace), and `acl` (principal list, PRO-1684: same contract as an app_knowledge item; omit for unrestricted). Contexts land in the memory corpus. | Deprecated alias for `context`, accepted for SDK releases that still send it. Send `context`; sending both is a 400. | 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) | Deprecated: knowledge files to ingest (repeatable; type=knowledge, split databases only) | Deprecated: per-document metadata as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Deprecated: app-knowledge items as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB, optional acl principal list (PRO-1684). | Deprecated: memory items as a JSON array (type=memory, split databases only); use context. Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Request-level enrichment default for `context` (true/false/1/0) | Request-level enrichment instructions default for `context` | Optional bring-your-own-graph payload as JSON, keyed by context_id (context) or source_id (split paths)", "required": true }, "responses": { @@ -8626,7 +10928,7 @@ } } }, - "description": "Body is not multipart/form-data (e.g. a JSON body)" + "description": "Body is neither multipart/form-data nor application/json" }, "422": { "content": { @@ -8785,7 +11087,7 @@ }, "/context/list": { "post": { - "description": "List knowledge sources or memories (id + metadata) for a tenant.", + "description": "List items (id + metadata) for a database: knowledge sources (default) or memories, selected by `type`.", "requestBody": { "content": { "application/json": { @@ -8832,6 +11134,139 @@ "x-fern-sdk-method-name": "list" } }, + "/context/profile": { + "get": { + "description": "Return the compiled profile of one entity in one collection: identity headline, a cited summary, and the current admitted entries (each pointing at the statements behind it). Profiles are maintained continuously by the ingestion pipeline's entity keeper; this endpoint reads the materialized view and never triggers recomputation.", + "parameters": [ + { + "description": "Database (canonical name for the tenant scope)", + "in": "query", + "name": "database", + "required": true, + "schema": { + "example": "acme_corp", + "type": "string" + } + }, + { + "description": "Collection (canonical name for the sub-tenant scope)", + "in": "query", + "name": "collection", + "required": true, + "schema": { + "example": "team_docs", + "type": "string" + } + }, + { + "description": "Deprecated alias for database", + "in": "query", + "name": "tenant_id", + "schema": { + "deprecated": true, + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + } + }, + { + "description": "Deprecated alias for collection", + "in": "query", + "name": "sub_tenant_id", + "schema": { + "deprecated": true, + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + } + }, + { + "description": "Entity whose profile to return (e.g. a person's name)", + "in": "query", + "name": "subject", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Corpus type: 'knowledge' or 'memory'", + "in": "query", + "name": "type", + "schema": { + "default": "memory", + "enum": [ + "knowledge", + "memory" + ], + "type": "string" + } + }, + { + "description": "Graph entity type of the subject", + "in": "query", + "name": "entity_type", + "schema": { + "default": "PERSON", + "type": "string" + } + }, + { + "description": "Graph namespace of the subject", + "in": "query", + "name": "namespace", + "schema": { + "default": "users", + "example": "organization", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.Envelope-search_EntityProfileView" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "No profile compiled for this subject yet, or the feature is not enabled" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Get entity profile", + "tags": [ + "context" + ], + "x-fern-sdk-group-name": "context", + "x-fern-sdk-method-name": "profile" + } + }, "/context/relations": { "get": { "description": "Return knowledge-graph relations for a tenant or a single source.", @@ -8887,13 +11322,14 @@ } }, { - "description": "Corpus type: 'knowledge' or 'memory'", + "description": "Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", "in": "query", "name": "type", "schema": { "enum": [ "knowledge", - "memory" + "memory", + "all" ], "type": "string" } @@ -9060,12 +11496,150 @@ "BearerAuth": [] } ], - "summary": "Check processing status", + "summary": "Check processing status", + "tags": [ + "context" + ], + "x-fern-sdk-group-name": "context", + "x-fern-sdk-method-name": "status" + } + }, + "/context/subgraph": { + "get": { + "description": "Query-string form of GET /context/{id}/subgraph: the same parameters, the same response, and the same rules. It exists for an id that contains '/', which cannot be spelled as one path segment; generated SDKs call this form for every id. Return the connected subgraph of one ingested item: every item reachable from it through item-level relations (explicit `relates_to` links, a shared thread, parent/child hierarchy, traversed breadth-first up to `depth` hops), the relations among those members, and the structural graph around them (entities, comments, attachments, actors). Chunk-level entity relations are not included; use Inspecting Context Relations for those. An unknown id returns an empty subgraph, not an error.", + "parameters": [ + { + "description": "Item ID: the ingested item whose connected subgraph to return. This form takes any id, including one that contains '/'.", + "in": "query", + "name": "id", + "required": true, + "schema": { + "example": "HydraDoc1234", + "type": "string" + } + }, + { + "description": "Database (canonical name for the tenant scope)", + "in": "query", + "name": "database", + "required": true, + "schema": { + "example": "acme_corp", + "type": "string" + } + }, + { + "description": "Collection (canonical name for the sub-tenant scope)", + "in": "query", + "name": "collection", + "schema": { + "example": "team_docs", + "type": "string" + } + }, + { + "description": "Deprecated alias for database", + "in": "query", + "name": "tenant_id", + "schema": { + "deprecated": true, + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + } + }, + { + "description": "Deprecated alias for collection", + "in": "query", + "name": "sub_tenant_id", + "schema": { + "deprecated": true, + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + } + }, + { + "description": "Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", + "in": "query", + "name": "type", + "schema": { + "enum": [ + "knowledge", + "memory", + "all" + ], + "type": "string" + } + }, + { + "description": "Max traversal depth in hops", + "in": "query", + "name": "depth", + "schema": { + "default": 5, + "maximum": 10, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Max members returned; `is_truncated` reports when this clipped the traversal", + "in": "query", + "name": "max_sources", + "schema": { + "default": 200, + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Principals to answer as (document ACLs): the subgraph contains only items they may see, filtered at every hop. Repeated (acl=a\u0026acl=b) or comma-separated. Omit for no ACL scoping.", + "in": "query", + "name": "acl", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.Envelope-graph_SourceSubgraphResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Bad Request" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Get connected subgraph", "tags": [ "context" ], "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "status" + "x-fern-sdk-method-name": "subgraph" } }, "/context/{id}/metadata": { @@ -9204,14 +11778,14 @@ } }, { - "description": "Corpus type: 'knowledge' or 'memory'", + "description": "Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", "in": "query", "name": "type", "schema": { - "default": "knowledge", "enum": [ "knowledge", - "memory" + "memory", + "all" ], "type": "string" } @@ -9248,15 +11822,311 @@ }, "type": "array" }, - "style": "form" + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.Envelope-graph_SourceSubgraphResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Bad Request" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Get connected subgraph", + "tags": [ + "context" + ], + "x-fern-ignore": true + } + }, + "/credential-vault": { + "get": { + "description": "List metadata and field names for credentials already used by connectors in the current workspace.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.vaultCredentialListResponse" + } + } + }, + "description": "OK" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "List connector credentials", + "tags": [ + "connectors" + ] + } + }, + "/credential-vault/{id}": { + "patch": { + "description": "Update fields on the credential currently used by a connector. Owner-only human action.", + "parameters": [ + { + "description": "Connector ID", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "HydraDoc1234", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.vaultCredentialUpdateReq" + } + } + }, + "description": "Credential fields", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.vaultCredentialUpdateResponse" + } + } + }, + "description": "OK" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Update connector credentials", + "tags": [ + "connectors" + ] + } + }, + "/credential-vault/{id}/reveal": { + "post": { + "description": "Reveal one field from the credential currently used by a connector in the workspace. The response must never be cached.", + "parameters": [ + { + "description": "Connector ID", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "HydraDoc1234", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.vaultCredentialRevealReq" + } + } + }, + "description": "Credential field", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.vaultCredentialRevealResponse" + } + } + }, + "description": "OK" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Reveal a connector credential value", + "tags": [ + "connectors" + ] + } + }, + "/databases": { + "delete": { + "description": "Delete a database and all associated data", + "parameters": [ + { + "description": "Database identifier to delete", + "in": "query", + "name": "database", + "required": true, + "schema": { + "example": "acme_corp", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantDeleteResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Delete a database", + "tags": [ + "database-management" + ], + "x-fern-sdk-group-name": "databases", + "x-fern-sdk-method-name": "delete" + }, + "get": { + "description": "List all databases for the authenticated user", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantIdsResponse" + } + } + }, + "description": "OK" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "security": [ + { + "BearerAuth": [] } ], + "summary": "List databases", + "tags": [ + "database-management" + ], + "x-fern-sdk-group-name": "databases", + "x-fern-sdk-method-name": "list" + }, + "post": { + "description": "Create a new database with optional custom metadata schema", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tenants.TenantCreateRequest" + } + } + }, + "description": "Database creation request", + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-graph_SourceSubgraphResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantCreateAcceptedResponse" } } }, @@ -9271,6 +12141,36 @@ } }, "description": "Bad Request" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Conflict" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" } }, "security": [ @@ -9278,20 +12178,20 @@ "BearerAuth": [] } ], - "summary": "Get connected subgraph", + "summary": "Create a database", "tags": [ - "context" + "database-management" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "subgraph" + "x-fern-sdk-group-name": "databases", + "x-fern-sdk-method-name": "create" } }, - "/databases": { + "/databases/collections": { "delete": { - "description": "Delete a database and all associated data", + "description": "Permanently remove one collection and all of its data from a database. The database itself is left intact.", "parameters": [ { - "description": "Database identifier to delete", + "description": "Database identifier", "in": "query", "name": "database", "required": true, @@ -9299,6 +12199,16 @@ "example": "acme_corp", "type": "string" } + }, + { + "description": "Collection identifier", + "in": "query", + "name": "collection", + "required": true, + "schema": { + "example": "team_docs", + "type": "string" + } } ], "responses": { @@ -9306,7 +12216,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantDeleteResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantDeleteResponse" } } }, @@ -9348,26 +12258,58 @@ "BearerAuth": [] } ], - "summary": "Delete a database", + "summary": "Delete a collection", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "delete" + "x-fern-sdk-method-name": "deleteCollection" }, "get": { - "description": "List all databases for the authenticated user", + "description": "List all collections for a given database", + "parameters": [ + { + "description": "Database identifier", + "in": "query", + "name": "database", + "required": true, + "schema": { + "example": "acme_corp", + "type": "string" + } + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantIdsResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantIdsResponse" } } }, "description": "OK" }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Not Found" + }, "500": { "content": { "application/json": { @@ -9384,32 +12326,35 @@ "BearerAuth": [] } ], - "summary": "List databases", + "summary": "List collections", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "list" - }, - "post": { - "description": "Create a new database with optional custom metadata schema", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tenants.TenantCreateRequest" - } + "x-fern-sdk-method-name": "collections" + } + }, + "/databases/stats": { + "get": { + "description": "Get collection statistics for a database", + "parameters": [ + { + "description": "Database identifier", + "in": "query", + "name": "database", + "required": true, + "schema": { + "example": "acme_corp", + "type": "string" } - }, - "description": "Database creation request", - "required": true - }, + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantCreateAcceptedResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantStatsResponse" } } }, @@ -9425,17 +12370,7 @@ }, "description": "Bad Request" }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { + "404": { "content": { "application/json": { "schema": { @@ -9443,7 +12378,7 @@ } } }, - "description": "Conflict" + "description": "Not Found" }, "500": { "content": { @@ -9461,17 +12396,17 @@ "BearerAuth": [] } ], - "summary": "Create a database", + "summary": "Get database statistics", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "create" + "x-fern-sdk-method-name": "stats" } }, - "/databases/collections": { + "/databases/status": { "get": { - "description": "List all collections for a given database", + "description": "Check the infrastructure provisioning status for a database", "parameters": [ { "description": "Database identifier", @@ -9489,7 +12424,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantIdsResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_InfraStatusResponseV2" } } }, @@ -9531,43 +12466,46 @@ "BearerAuth": [] } ], - "summary": "List collections", + "summary": "Get infrastructure status", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "collections" - }, - "delete": { - "description": "Permanently remove one collection and all of its data from a database. The database itself is left intact and its other collections are untouched. `database` and `collection` are both required. The API still accepts the deprecated `tenant_id` and `sub_tenant_id` aliases in their place, but generated clients should send the canonical names.", + "x-fern-sdk-method-name": "status" + } + }, + "/databases/{database}": { + "patch": { + "description": "Rename a database in place. The internal identity (and therefore all indexed data, graphs and documents) is unchanged — only the caller-facing name moves, atomically. Connectors syncing into the database are repointed at the new name. The old name stops resolving immediately, so callers must switch to the new name in the same rollout.", "parameters": [ { - "description": "Database identifier. The API also accepts the deprecated `tenant_id` alias in its place; this operation models only the canonical name, as every other operation in this spec does.", - "in": "query", + "description": "Current database identifier", + "in": "path", "name": "database", "required": true, "schema": { "example": "acme_corp", "type": "string" } - }, - { - "description": "Collection identifier. Unlike the read endpoints this does not default to the database's own collection, because a delete has no safe default. The API also accepts the deprecated `sub_tenant_id` alias in its place; this operation models only the canonical name.", - "in": "query", - "name": "collection", - "required": true, - "schema": { - "example": "engineering", - "type": "string" - } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tenants.TenantRenameRequest" + } + } + }, + "description": "New database name", + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantDeleteResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantRenameResponse" } } }, @@ -9593,6 +12531,16 @@ }, "description": "Not Found" }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Conflict" + }, "500": { "content": { "application/json": { @@ -9609,21 +12557,21 @@ "BearerAuth": [] } ], - "summary": "Delete a collection", + "summary": "Rename a database", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "deleteCollection" + "x-fern-sdk-method-name": "rename" } }, - "/databases/stats": { + "/databases/{database}/instructions": { "get": { - "description": "Get collection statistics for a database", + "description": "Read the custom ingestion instructions configured for a database and for its collections.", "parameters": [ { "description": "Database identifier", - "in": "query", + "in": "path", "name": "database", "required": true, "schema": { @@ -9637,7 +12585,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantStatsResponse" + "$ref": "#/components/schemas/handler.instructionsResponse" } } }, @@ -9679,21 +12627,19 @@ "BearerAuth": [] } ], - "summary": "Get database statistics", + "summary": "Get ingestion instructions", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "stats" - } - }, - "/databases/status": { - "get": { - "description": "Check the infrastructure provisioning status for a database", + "x-fern-sdk-method-name": "get_instructions" + }, + "patch": { + "description": "Set or clear the custom ingestion instructions for a database and its collections. Database instructions apply to every document; a collection's instructions apply on top of them. Both stack with any connector- or resource-level instructions rather than replacing them. Applies from the next ingestion; already-indexed data is not reprocessed.", "parameters": [ { "description": "Database identifier", - "in": "query", + "in": "path", "name": "database", "required": true, "schema": { @@ -9702,12 +12648,23 @@ } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.instructionsUpdateReq" + } + } + }, + "description": "Instructions to set or clear", + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_InfraStatusResponseV2" + "$ref": "#/components/schemas/handler.instructionsResponse" } } }, @@ -9749,12 +12706,12 @@ "BearerAuth": [] } ], - "summary": "Get infrastructure status", + "summary": "Update ingestion instructions", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "status" + "x-fern-sdk-method-name": "update_instructions" } }, "/databases/{database}/metadata-schema": { @@ -9987,7 +12944,7 @@ }, "/query": { "post": { - "description": "Unified query endpoint that dispatches across type (knowledge/memory/all) and query_by (hybrid/text). Prefer sub_tenant_ids for sub-tenant scoping; legacy sub_tenant_id is deprecated for /query and cannot be sent together with sub_tenant_ids.", + "description": "Unified query endpoint that dispatches across type and query_by (hybrid/text). Optionally filter by one or more exact document titles with `titles`; these are resolved to source IDs before normal retrieval. Filter with `attributes` (an operator language, pushed into the vector search); `metadata_filters` is deprecated in favour of it and still works. `type` is knowledge (the default), memory, or all (both, merged). Prefer sub_tenant_ids for sub-tenant scoping; legacy sub_tenant_id is deprecated for /query and cannot be sent together with sub_tenant_ids.", "requestBody": { "content": { "application/json": { diff --git a/api-reference/v2/sdks.mdx b/api-reference/v2/sdks.mdx index b3e2239a..c0dcf238 100644 --- a/api-reference/v2/sdks.mdx +++ b/api-reference/v2/sdks.mdx @@ -59,7 +59,7 @@ The REST API uses **snake_case** for every request and response field. The Pytho | **TypeScript SDK** | camelCase | camelCase | `client.query({ maxResults: 8 })`, `result.data.llmPrompt` | -**Item keys stay snake_case in every language.** `client.context.ingest` takes the item list as a JSON string in the `items` field, so the keys inside each item (`context_id`, `happened_at`, `custom_attributes`) are the wire names in TypeScript too. +**Item keys stay snake_case in every language.** `client.context.ingest` takes the item list as a JSON string in the `context` field, so the keys inside each item (`context_id`, `happened_at`, `custom_attributes`) are the wire names in TypeScript too. `database` and `collection` are the current field names (formerly `tenant_id` and `sub_tenant_id`). The old names remain accepted as deprecated aliases. @@ -179,7 +179,7 @@ while (true) { ### Ingest context -Everything you ingest is a context item: one `text` or one `conversation`, with optional fields such as `context_id`, `title`, `happened_at` and `attributes`. The SDK sends a multipart form and puts the item list, as a JSON string, in the `items` field. +Everything you ingest is a context item: one `text` or one `conversation`, with optional fields such as `context_id`, `title`, `happened_at` and `attributes`. The SDK sends a multipart form and puts the item list, as a JSON string, in the `context` field. SDK releases generated from the current API spec take `context`; older releases take `items`, which the API still accepts as a deprecated alias. ```python Python SDK @@ -188,7 +188,7 @@ import json result = client.context.ingest( database="my_first_database", collection="support", - items=json.dumps([ + context=json.dumps([ { "context_id": "refund-policy", "title": "Refund policy", @@ -212,7 +212,7 @@ print([r.id for r in result.data.results]) const result = await client.context.ingest({ database: "my_first_database", collection: "support", - items: JSON.stringify([ + context: JSON.stringify([ { context_id: "refund-policy", title: "Refund policy", diff --git a/essentials/v2/attributes.mdx b/essentials/v2/attributes.mdx index 746126bd..374d4180 100644 --- a/essentials/v2/attributes.mdx +++ b/essentials/v2/attributes.mdx @@ -178,7 +178,7 @@ The update is additive only: ## 3. Attach attributes at ingest -Send `attributes` and `custom_attributes` on each item in `context` on [`POST /context/ingest`](/essentials/v2/ingest). The SDKs send the same item array, as a JSON string, in the `items` form field; keys inside each item stay snake_case in every language. +Send `attributes` and `custom_attributes` on each item in `context` on [`POST /context/ingest`](/essentials/v2/ingest). The SDKs send the same item array, as a JSON string, in the `context` form field; keys inside each item stay snake_case in every language. ```bash cURL @@ -214,7 +214,7 @@ import json client.context.ingest( database="acme_corp", collection="company", - items=json.dumps([ + context=json.dumps([ { "context_id": "auth-controls-001", "title": "Authentication controls", @@ -234,7 +234,7 @@ client.context.ingest( await client.context.ingest({ database: "acme_corp", collection: "company", - items: JSON.stringify([ + context: JSON.stringify([ { context_id: "auth-controls-001", title: "Authentication controls", diff --git a/essentials/v2/bring-your-own-graph.mdx b/essentials/v2/bring-your-own-graph.mdx index b0bc6bd0..99d9bd99 100644 --- a/essentials/v2/bring-your-own-graph.mdx +++ b/essentials/v2/bring-your-own-graph.mdx @@ -67,7 +67,7 @@ Pick the right tool: - **No `chunk_id`:** you never supply chunk ids. HydraDB links your relations to the item's chunks server-side. - Entity names are **normalized (lowercased)** so they match at query time, just like extracted entities. Entities that no relation references are dropped. -In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, where `graph_payload` is a JSON string next to the `items` field; see the [examples](#6-example-several-items-in-one-request). +In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, where `graph_payload` is a JSON string next to the `context` field; see the [examples](#6-example-several-items-in-one-request). --- @@ -196,7 +196,7 @@ graphs = { client.context.ingest( database="acme_corp", - items=json.dumps(items), + context=json.dumps(items), graph_payload=json.dumps(graphs), ) ``` @@ -246,7 +246,7 @@ const graphs = { await client.context.ingest({ database: "acme_corp", - items: JSON.stringify(items), + context: JSON.stringify(items), graphPayload: JSON.stringify(graphs), }); ``` diff --git a/essentials/v2/context-categories.mdx b/essentials/v2/context-categories.mdx index 99606d26..a2044d80 100644 --- a/essentials/v2/context-categories.mdx +++ b/essentials/v2/context-categories.mdx @@ -59,7 +59,7 @@ import json client.context.ingest( database="acme", collection="user_alex", - items=json.dumps([{ + context=json.dumps([{ "context_id": "chat-alex-001", "conversation": [ {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, @@ -74,7 +74,7 @@ client.context.ingest( await client.context.ingest({ database: "acme", collection: "user_alex", - items: JSON.stringify([{ + context: JSON.stringify([{ context_id: "chat-alex-001", conversation: [ { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, @@ -87,7 +87,7 @@ await client.context.ingest({ ``` -The SDKs send the array in the `items` form field; the JSON body calls the list `context`. See [Ingest context](/essentials/v2/ingest#1-one-call-for-text-and-conversations). +The SDKs send the same `context` array as a JSON string in a form field of the same name. See [Ingest context](/essentials/v2/ingest#1-one-call-for-text-and-conversations). Recall it by querying that person's collection: diff --git a/essentials/v2/databases-and-collections.mdx b/essentials/v2/databases-and-collections.mdx index a22bc267..20dc7b67 100644 --- a/essentials/v2/databases-and-collections.mdx +++ b/essentials/v2/databases-and-collections.mdx @@ -173,11 +173,11 @@ const client = new HydraDBClient({ }); // 1. Write a person's preference under their own collection. -// The SDK sends the item list in the `items` form field. +// The SDK sends the item list in the `context` form field. await client.context.ingest({ database: "acme_corp", collection: "user_123", - items: JSON.stringify([ + context: JSON.stringify([ { text: "Prefers dark mode and short answers.", user_name: "John", @@ -203,11 +203,11 @@ from hydra_db import HydraDB client = HydraDB(token=os.environ["HYDRA_DB_API_KEY"]) # 1. Write a person's preference under their own collection. -# The SDK sends the item list in the `items` form field. +# The SDK sends the item list in the `context` form field. client.context.ingest( database="acme_corp", collection="user_123", - items=json.dumps([ + context=json.dumps([ { "text": "Prefers dark mode and short answers.", "user_name": "John", diff --git a/essentials/v2/ingest.mdx b/essentials/v2/ingest.mdx index 6099e31f..c2d1c033 100644 --- a/essentials/v2/ingest.mdx +++ b/essentials/v2/ingest.mdx @@ -45,7 +45,7 @@ import json ingest = client.context.ingest( database="acme", collection="company", - items=json.dumps([ + context=json.dumps([ { "context_id": "refund-policy", "title": "Refund policy", @@ -70,7 +70,7 @@ print([r.id for r in ingest.data.results]) const ingest = await client.context.ingest({ database: "acme", collection: "company", - items: JSON.stringify([ + context: JSON.stringify([ { context_id: "refund-policy", title: "Refund policy", @@ -94,7 +94,7 @@ console.log(ingest.data.results.map((r) => r.id)); -**SDK users: the form field is still called `items`.** The SDKs send a multipart form rather than a JSON body, and the array goes in the `items` form field, which is why `items` is a JSON string there. The SDK methods also take `database`, `collection`, `upsert` and `graph_payload`; to set `enrich` or `instructions` through an SDK, set them on each item. Both entry points run the same validation. Prefer the JSON body with `context` when you call the API directly. Keys inside each item stay `snake_case` in every language. +**SDK users: `context` is a JSON string.** The SDKs send a multipart form rather than a JSON body, and the array goes in the `context` form field, which is why `context` is a JSON string there. SDK releases generated from the current API spec take `context`; older releases take `items`, which the API still accepts as a deprecated alias. The SDK methods also take `database`, `collection`, `upsert` and `graph_payload`; to set `enrich` or `instructions` through an SDK, set them on each item. Both entry points run the same validation. Prefer the JSON body with `context` when you call the API directly. Keys inside each item stay `snake_case` in every language. The response is `202 Accepted`: @@ -132,7 +132,7 @@ A `202` means the items were accepted and queued, not that they are searchable y | --- | --- | | `database` | Required. The database to write to. | | `collection` | Optional. The collection to write to; the default collection when omitted. | -| `context` | The list of items, at most 100. `items` and `contexts` are accepted as aliases; send `context`. | +| `context` | The list of items, at most 100. | | `enrich` | Request-level default for every item's `enrich`. Default `true`. | | `upsert` | Request-level default for every item's `upsert`. Default `true`. | | `instructions` | Request-level default for every item's `instructions`. Default empty. | diff --git a/essentials/v2/split-databases.mdx b/essentials/v2/split-databases.mdx index 35da3bff..ed460dd0 100644 --- a/essentials/v2/split-databases.mdx +++ b/essentials/v2/split-databases.mdx @@ -231,7 +231,7 @@ In the **indexing webhook payload**, `tenant_id` and `database` do not carry the | `custom_attributes` | `additional_metadata` | - The ingest `202` also reports each item as `results[].source_id`; read it as the `context_id`. -- `items` and `contexts` are accepted on ingest as aliases of `context`; `content` as an alias of `text`; `messages` as an alias of `conversation`. +- `content` is accepted on ingest as an alias of `text`, and `messages` as an alias of `conversation`. - On a unified database, `GET /databases/stats` reports the database's indexed chunk count in `knowledge_collection.row_count`, and `memory_collection` repeats the same number. --- diff --git a/get-started/v2/core-concepts.mdx b/get-started/v2/core-concepts.mdx index d70cdcd7..5dd5ff96 100644 --- a/get-started/v2/core-concepts.mdx +++ b/get-started/v2/core-concepts.mdx @@ -51,7 +51,7 @@ An item is either plain `text` or a `conversation`: a document, a policy, a supp } ``` -Enrichment is on by default (`enrich: true`): send raw conversations and logs, and HydraDB extracts the preferences and facts in them, stored separately from the item's own text. Turn it off for items you want stored exactly as sent. The SDKs send the same list in the `items` form field. +Enrichment is on by default (`enrich: true`): send raw conversations and logs, and HydraDB extracts the preferences and facts in them, stored separately from the item's own text. Turn it off for items you want stored exactly as sent. The SDKs send the same list in the `context` form field. Read more: [Ingest context](/essentials/v2/ingest) diff --git a/get-started/v2/quickstart.mdx b/get-started/v2/quickstart.mdx index a6f54021..76e64e6c 100644 --- a/get-started/v2/quickstart.mdx +++ b/get-started/v2/quickstart.mdx @@ -60,11 +60,11 @@ while True: time.sleep(5) # 3. Ingest a policy into the shared collection and a conversation into Alex's. -# The SDK sends the item list in the `items` form field. +# The SDK sends the item list in the `context` form field. client.context.ingest( database=database, collection="company", - items=json.dumps([{ + context=json.dumps([{ "context_id": "refund-policy", "title": "Refund policy", "text": "Refunds are processed within 5 business days.", @@ -73,7 +73,7 @@ client.context.ingest( client.context.ingest( database=database, collection="user_alex", - items=json.dumps([{ + context=json.dumps([{ "context_id": "chat-alex-001", "conversation": [ {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, @@ -123,11 +123,11 @@ while (true) { } // 3. Ingest a policy into the shared collection and a conversation into Alex's. -// The SDK sends the item list in the `items` form field. +// The SDK sends the item list in the `context` form field. await client.context.ingest({ database, collection: "company", - items: JSON.stringify([{ + context: JSON.stringify([{ context_id: "refund-policy", title: "Refund policy", text: "Refunds are processed within 5 business days.", @@ -136,7 +136,7 @@ await client.context.ingest({ await client.context.ingest({ database, collection: "user_alex", - items: JSON.stringify([{ + context: JSON.stringify([{ context_id: "chat-alex-001", conversation: [ { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, From 4b634f82a856bec6b33ec11b93c5fb0da64ce9db Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Wed, 23 Sep 2026 19:26:02 +0530 Subject: [PATCH 02/17] docs: ingest pages match the strict `context` contract on app staging (PRO-1618) Copy application/docs/openapi.json from hydradb-application staging at 1d405fdf330c (the spec last changed in b8adfeffc7a1) to api-reference/v2/openapi.json. It publishes `context` as the one list field, in the JSON body and in the multipart form, with no `items`. The ingest pages now say what the server does: - `items` is gone, with no alias; the sentences about older SDK releases taking it are removed. - Unknown keys are a 400 that names the key and lists the accepted ones, on the body, an item, a conversation turn and inside forceful_relations. No page says they are ignored any more. - forceful_relations is `{context_ids, properties}`; the properties rules (flat scalars, 1 KiB, reserved keys) are listed. - A conversation turn is `{role, content}`. The speaker is the item's `user_name`, so the examples move the per-turn `name` there. `is_markdown` is removed. - title is at most 1,024 bytes; instructions at most 4,000 characters, which system turns count against when they become the item's instructions. - The SDK methods take enrich and instructions as form fields; upsert and enrich on the form accept true, false, 1 or 0 only. - The hidden split-databases page no longer lists aliases a unified database accepts; it accepts none. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 37 ++-- api-reference/v2/endpoint/ingest-context.mdx | 34 ++-- .../v2/endpoint/sources-overview.mdx | 2 +- api-reference/v2/error-responses.mdx | 6 +- api-reference/v2/openapi.json | 176 ++++-------------- api-reference/v2/sdks.mdx | 8 +- essentials/v2/context-categories.mdx | 9 +- essentials/v2/ingest.mdx | 53 +++--- essentials/v2/split-databases.mdx | 6 +- get-started/v2/core-concepts.mdx | 3 +- get-started/v2/quickstart.mdx | 6 +- 11 files changed, 133 insertions(+), 207 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index ebb25b4c..82b8c878 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -208,7 +208,7 @@ Recommended patterns: An item is one piece of context, and carries exactly one of: - `text`: a document, a note, a policy, an agent log line; anything you already have as a string. To ingest a file, extract its text first. -- `conversation`: a list of `{ role, content, name? }` turns, the same message list you already send to OpenAI or Anthropic. +- `conversation`: a list of `{ role, content }` turns, the same message list you already send to OpenAI or Anthropic. Every item can also carry a `context_id` (your id; reuse it to replace the item), a `title`, declared `attributes` and free-form `custom_attributes`, a `happened_at` date, `forceful_relations` to other items, an `acl`, and per-item `enrich` / `upsert` / `instructions`. See [Item fields](#item-fields). @@ -259,7 +259,7 @@ At ingest, any item (text or conversation) can declare which other items it is l { "context_id": "refund-policy", "text": "Refunds are processed within 5 business days.", - "forceful_relations": { "ids": ["refund-faq", "refund-escalations"], "properties": {} } + "forceful_relations": { "context_ids": ["refund-faq", "refund-escalations"], "properties": {} } } ``` @@ -268,7 +268,7 @@ At query time, in `thinking` mode and with `follow_forceful_relations: true` (th Rules: - Forceful relations are followed only in `thinking` mode (including when `mode: "auto"` routes a query to thinking). -- `ids` are `context_id`s. +- `context_ids` are `context_id`s. - They are linked by the author, not ranked for the query, so do not read their `score` as relevance. --- @@ -361,8 +361,9 @@ client.context.ingest( collection="user_alex", context=json.dumps([{ "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, + {"role": "user", "content": "Keep answers short, I read on my phone."}, {"role": "assistant", "content": "Got it, short answers."}, ], "happened_at": "2026-09-01", @@ -436,8 +437,9 @@ await client.context.ingest({ collection: "user_alex", context: JSON.stringify([{ context_id: "chat-alex-001", + user_name: "alex", conversation: [ - { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, + { role: "user", content: "Keep answers short, I read on my phone." }, { role: "assistant", content: "Got it, short answers." }, ], happened_at: "2026-09-01", @@ -627,12 +629,13 @@ One endpoint takes every item, text or conversation, into any collection of a da "text": "Refunds are processed within 5 business days.", "attributes": { "department": "support" }, "custom_attributes": { "owner": "sam@acme.com" }, - "forceful_relations": { "ids": ["refund-faq"], "properties": {} } + "forceful_relations": { "context_ids": ["refund-faq"], "properties": {} } }, { "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - { "role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex" }, + { "role": "user", "content": "Keep answers short, I read on my phone." }, { "role": "assistant", "content": "Got it, short answers." } ], "happened_at": "2026-09-01", @@ -664,28 +667,27 @@ Each item carries exactly one of `text` or `conversation`. | Field | Notes | |---|---| | `context_id` | Your id for the item. Generated from `title` when omitted, so two untitled items without ids and with the same text collide. Must not contain commas. | -| `title` | Optional readable name, printed in `llm_prompt` and matchable with `titles` on `/query`. | +| `title` | Optional readable name, printed in `llm_prompt` and matchable with `titles` on `/query`. At most 1,024 bytes. | | `text` | Plain text or markdown. | -| `conversation` | A list of `{ role, content, name? }` turns. | +| `conversation` | A list of `{ role, content }` turns. | | `enrich` | Extract entities, relations and preferences from this item. Default: the request's `enrich`, else `true`. Set `false` to store the item only as searchable text. | | `upsert` | Replace an existing item with the same `context_id`. Default: the request's `upsert`, else `true`. | -| `instructions` | Steer enrichment for this item. Default: the request's `instructions`. | +| `instructions` | Steer enrichment for this item. At most 4,000 characters. Default: the request's `instructions`. | | `happened_at` | The date the item is about, `YYYY-MM-DD` only; a timestamp is a `400`. HydraDB records when it received the item separately. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. | | `custom_attributes` | Free-form fields. Not filterable. | -| `forceful_relations` | `{ "ids": [...], "properties": {} }`: the `context_id`s this item is linked to. | +| `forceful_relations` | `{ "context_ids": [...], "properties": {} }`: the `context_id`s this item is linked to. `properties` is an optional flat map of string, number or boolean values (at most 1 KiB) stored on each edge; `id`, `created_at`, `relation_type`, `tenant_id` and `sub_tenant_id` are reserved keys. | | `acl` | Principals allowed to retrieve the item: `user_email:a@x.com` (or a bare email), `group::`, `domain:acme.com`, `__public__`. Omit for unrestricted, `[]` for nobody. A malformed principal rejects the whole request with `400`. | -| `is_markdown` | Chunk `text` on its markdown structure instead of as flat prose. | -| `user_name` | The speaker for a text item. On a conversation, the per-turn `name` wins. | +| `user_name` | The speaker for the item: the author of a text item, or the person in a conversation's `user` turns. Default `"User"`. | -A key an item does not recognise is dropped without an error, so check spelling against this table. +An unknown key is a `400` naming the key and listing the accepted ones, whether it is on the request, on an item, on a conversation turn or inside `forceful_relations`. ### Conversations - Roles are `user`, `assistant` and `system`. Any other role is a `400`; map roles such as `tool` or `human` before sending. -- `system` turns shape enrichment but are never stored as facts. A conversation of only `system` turns is a `400`. +- `system` turns are never stored as facts. When neither the item nor the request sets `instructions`, they become the item's instructions, held to the same 4,000-character limit; otherwise they are dropped. A conversation of only `system` turns is a `400`. - Consecutive turns with the same role are accepted and joined. -- Set `name` per turn when several people speak, so preferences are attributed to the right person. +- The speaker is the item's `user_name`. A turn carries only `role` and `content`; any other key on a turn is a `400`. - An empty list, or a turn with empty `content`, is a `400`. ### IDs and replacement @@ -723,13 +725,14 @@ Every key in `graph_payload` must equal the `context_id` of an item in the same ### Limits and validation - At most **100 items** per request, **1 MiB** of text per item, **8 MiB** of text per request. Split larger batches. +- `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each item. - `attributes` are capped at **16 KiB** and `custom_attributes` at **1 KiB** per item, measured on the compact JSON encoding in UTF-8 bytes (keys and punctuation count). - A validation error names the item it refers to as `context[N]`. - Ingest takes text only. To ingest a PDF, DOCX or CMS export, extract its text in your application and send it as `text`, one item per document. For tools such as Slack, Notion or Google Drive, use a [connector](/essentials/v2/connectors): synced content lands in the same database and is queried together with your items. ### SDKs: the `context` form field -The SDKs send a multipart form rather than a JSON body. The item array goes in the `context` form field as a JSON string, next to `database`, `collection`, `upsert` and `graph_payload`; the server runs the same validation on both entry points. Set `enrich` and `instructions` on each item. Python: `client.context.ingest(database=..., collection=..., context=json.dumps([...]))`. TypeScript: `await client.context.ingest({ database, collection, context: JSON.stringify([...]) })`. Keys inside each item stay snake_case in both. Full examples are in [Minimal end-to-end flow](#4-minimal-end-to-end-flow). SDK releases generated from the current API spec take `context`; older releases take `items`, which the API still accepts as a deprecated alias. +The SDKs send a multipart form rather than a JSON body. The item array goes in the `context` form field as a JSON string, next to `database`, `collection`, `upsert`, `enrich`, `instructions` and `graph_payload`; the server runs the same validation on both entry points. On the form, `upsert` and `enrich` are `"true"`, `"false"`, `"1"` or `"0"`, and any other value is a `400`. Python: `client.context.ingest(database=..., collection=..., context=json.dumps([...]))`. TypeScript: `await client.context.ingest({ database, collection, context: JSON.stringify([...]) })`. Keys inside each item stay snake_case in both. Full examples are in [Minimal end-to-end flow](#4-minimal-end-to-end-flow). ### Response diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index a8bc2ee0..59cb6231 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -30,12 +30,13 @@ result = client.context.ingest( }, { "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, + {"role": "user", "content": "Keep answers short, I read on my phone."}, {"role": "assistant", "content": "Got it, short answers."}, ], "happened_at": "2026-09-01", - "forceful_relations": {"ids": ["refund-policy"]}, + "forceful_relations": {"context_ids": ["refund-policy"]}, }, ]), ) @@ -58,12 +59,13 @@ const result = await client.context.ingest({ }, { context_id: "chat-alex-001", + user_name: "alex", conversation: [ - { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, + { role: "user", content: "Keep answers short, I read on my phone." }, { role: "assistant", content: "Got it, short answers." }, ], happened_at: "2026-09-01", - forceful_relations: { ids: ["refund-policy"] }, + forceful_relations: { context_ids: ["refund-policy"] }, }, ]), }); @@ -91,12 +93,13 @@ curl -X POST 'https://api.hydradb.com/context/ingest' \ }, { "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - { "role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex" }, + { "role": "user", "content": "Keep answers short, I read on my phone." }, { "role": "assistant", "content": "Got it, short answers." } ], "happened_at": "2026-09-01", - "forceful_relations": { "ids": ["refund-policy"] } + "forceful_relations": { "context_ids": ["refund-policy"] } } ] }' @@ -108,7 +111,7 @@ curl -X POST 'https://api.hydradb.com/context/ingest' \ Send `application/json`. The SDKs send `multipart/form-data` instead: the same array goes in the `context` form field as a JSON string, and the request-level fields are form fields of the same name (`graph_payload` also as a JSON string). Both entry points run the same validation. Keys inside each item stay snake_case in every language. -The SDK `ingest` methods take `database`, `collection`, `context`, `upsert` (the string `"true"` or `"false"`) and `graph_payload`. To set `enrich` or `instructions` through an SDK, set them on each item. SDK releases generated from the current API spec take `context`; older releases take `items`, which the API still accepts as a deprecated alias. +The SDK `ingest` methods take `database`, `collection`, `context`, `upsert`, `enrich`, `instructions` and `graph_payload`. On the form, `upsert` and `enrich` are strings: `"true"`, `"false"`, `"1"` or `"0"`; any other value is a `400`. `enrich` and `instructions` can also be set on each item. | Name | Description | | --- | --- | @@ -117,7 +120,7 @@ The SDK `ingest` methods take `database`, `collection`, `context`, `upsert` (the | | The items to ingest, at least 1 and at most 100. In the multipart form the field is also `context`, a JSON-encoded array. | | | Request-level default for every item's `enrich`. (default=`true`) | | | Request-level default for every item's `upsert`. (default=`true`) | -| | Request-level default for every item's `instructions`. (default=empty) | +| | Request-level default for every item's `instructions`. At most 4,000 characters after trimming. (default=empty) | | | Bring your own graph: a map of `context_id` to `{ entities, relations }` that replaces graph extraction for that item. Every key must match the `context_id` of an item in the same request, otherwise `400`. See [Bring your own graph](#bring-your-own-graph) below. | ### Item fields @@ -127,29 +130,30 @@ Each item is exactly one of `text` or `conversation`. | Name | Description | | --- | --- | | | Your id for the item; the upsert key. Generated when omitted. At most 100 bytes. Must not contain a comma (`,`), which is the id separator on `/context/status?ids=`, and must not start with `att_` or `cmt_` (reserved for connector ids). | -| | Readable name. Searchable with `titles` on `/query`. | +| | Readable name. Searchable with `titles` on `/query`. Trimmed, then at most 1,024 bytes of UTF-8. | | | Plain text. Send exactly one of `text` or `conversation`. | -| | Turns of `{ role, content, name? }`; roles are `user`, `assistant` and `system`. `system` turns shape enrichment but are never stored as facts. A conversation needs at least one `user` or `assistant` turn, and no turn may have empty `content`. | +| | Turns of `{ role, content }`; roles are `user`, `assistant` and `system`. A turn takes no other key; the speaker is the item's `user_name`. `system` turns are never stored as facts: when neither the item nor the request sets `instructions`, they become the item's instructions and count toward the same 4,000-character limit; otherwise they are dropped. A conversation needs at least one `user` or `assistant` turn, and no turn may have empty `content`. | | | Extract entities, relations and preferences from this item into the graph; the output is stored separately and returned as `enrichment` on query. (default=the request's `enrich`, else `true`) | | | Replace an existing item with the same `context_id`, deleting its chunks and graph contribution first. (default=the request's `upsert`, else `true`) | -| | Steer enrichment for this item. (default=the request's `instructions`) | +| | Steer enrichment for this item. At most 4,000 characters after trimming. (default=the request's `instructions`) | | | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately. | | | Declared, filterable fields; keys must be in `database_metadata_schema`. Filter with `attributes` on `/query`. See [Attributes](/essentials/v2/attributes). | | | Free-form fields. Stored with the item; not filterable and not returned on query chunks. | -| | Relations you declare to other items: `{ "ids": ["", ...], "properties": {} }`. Followed on `/query` in `thinking` mode with `follow_forceful_relations` and returned in `forceful_relations[]`. Each id follows the same rules as `context_id`. | +| | Relations you declare to other items: `{ "context_ids": ["", ...], "properties": {} }`. Followed on `/query` in `thinking` mode with `follow_forceful_relations` and returned in `forceful_relations[]`. Each id follows the same rules as `context_id`. `properties` is optional and is stored on every edge the item declares: a flat map of string, number or boolean values, at most 1 KiB as compact JSON, with no empty key and none of the reserved keys `id`, `created_at`, `relation_type`, `tenant_id` or `sub_tenant_id`. | | | Principals allowed to retrieve the item: bare emails or `user_email:`, `group:`, `domain:` principals, or `__public__`. Omit for unrestricted; `[]` for nobody. A malformed principal rejects the whole request with `400`. See [Access control](/essentials/v2/access-control). | -| | Chunk `text` on its markdown structure instead of as flat prose. (default=`false`) | -| | The speaker for a text item. On a conversation each turn's `name` wins. (default=`"User"`) | +| | The speaker for the item: the author of a text item, or the person in a conversation's `user` turns. (default=`"User"`) | ### Limits - At most **100 items** per request, **1 MiB** of text per item, **8 MiB** of text per request. Text is an item's `text`, or the `content` of every turn of its `conversation`; titles and attributes are not counted. - `attributes` at most **16 KiB** and `custom_attributes` at most **1 KiB** per item, measured on their compact JSON encoding. +- `title` at most **1,024 bytes**, and `instructions` at most **4,000 characters** on the request and on each item. A conversation's `system` turns are held to the same 4,000 characters when they become the item's instructions. - The request is validated before anything is queued: one invalid item rejects the whole request with `400`, and the error names the item as `context[N]`. +- Unknown keys are refused. An unknown key at the top level of the body, on an item, on a conversation turn or inside `forceful_relations` is a `400` that names the key and lists the accepted ones. The same rule applies to the JSON in the `context` form field. ### Text only -Every item is text or a conversation. To ingest a file, extract its text and send it as a `text` item (with `is_markdown: true` if the extracted text is markdown). Content from connected apps arrives through [connectors](/essentials/v2/connectors). An item key HydraDB does not recognise is ignored without an error, so check field names against the table above. +Every item is text or a conversation. To ingest a file, extract its text and send it as a `text` item. Content from connected apps arrives through [connectors](/essentials/v2/connectors). ## Bring your own graph diff --git a/api-reference/v2/endpoint/sources-overview.mdx b/api-reference/v2/endpoint/sources-overview.mdx index 55a1a6e1..5f957168 100644 --- a/api-reference/v2/endpoint/sources-overview.mdx +++ b/api-reference/v2/endpoint/sources-overview.mdx @@ -71,7 +71,7 @@ Paired with declared attributes, you get deterministic control over how results "text": "1. Merge to main. 2. Wait for the image build. 3. Promote in ArgoCD.", "attributes": { "department": "ops" }, "custom_attributes": { "owner": "platform-team" }, - "forceful_relations": { "ids": ["monitoring_guide"] } + "forceful_relations": { "context_ids": ["monitoring_guide"] } } ] } diff --git a/api-reference/v2/error-responses.mdx b/api-reference/v2/error-responses.mdx index e9b26936..2f664fb6 100644 --- a/api-reference/v2/error-responses.mdx +++ b/api-reference/v2/error-responses.mdx @@ -251,13 +251,15 @@ Database creation is asynchronous. After `POST /databases`, poll [`GET /database `POST /context/ingest` validates every item before queuing any of them, and the error message names the failing item as `context[N]`. Common causes: +- The body, an item, a conversation turn or `forceful_relations` carries a key the API does not accept. The error names the key and lists the accepted fields, for example `invalid request body: unknown field "is_markdown"; accepted request fields are ...`. - An item has neither `text` nor `conversation`, or has both. - A conversation turn has a role other than `user`, `assistant` or `system`, or empty `content`; or the conversation has only `system` turns. - `happened_at` is not a `YYYY-MM-DD` date. -- A `context_id` contains a comma. +- A `context_id`, or an id in `forceful_relations.context_ids`, contains a comma, is longer than 100 bytes, or starts with `att_` or `cmt_`. +- `forceful_relations.properties` has a nested value, an empty key or a reserved key, or is over 1 KiB. - A `graph_payload` key matches no `context_id` in the same request. - An `acl` entry is not a valid principal. -- The request exceeds the limits: 100 items, 1 MiB of text per item, 8 MiB of text per request. +- The request exceeds the limits: 100 items, 1 MiB of text per item, 8 MiB of text per request, 1,024 bytes per `title`, 4,000 characters of `instructions`. ### Empty query results diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index 4e8a15a2..82115a48 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -825,6 +825,11 @@ "example": "Ada joined Acme Corp in 2024 as a staff engineer.", "type": "string" }, + "properties": { + "additionalProperties": {}, + "description": "Properties are the caller's own properties on a forceful_relation edge,\nexactly as declared at ingest (`forceful_relations.properties`). Flat\nscalars. Omitted on every other edge and on a forceful relation that\ndeclared none.", + "type": "object" + }, "raw_predicate": { "description": "As-extracted predicate before normalization.", "example": "is employed by", @@ -5384,6 +5389,10 @@ "example": true, "type": "boolean" }, + "context_category": { + "description": "ContextCategory is the context category the caller pinned on ingest:\none of user_preference, business_knowledge or decision_trace, written\nonto the source row by the ingestion pipeline (PRO-1618). Absent when no\ncategory was pinned, on rows ingested before the category existed, and\non every split-database row, which carries no category.", + "type": "string" + }, "database": { "description": "Database is the canonical name for the scope this row was listed from.", "example": "acme_corp", @@ -5510,6 +5519,10 @@ "example": true, "type": "boolean" }, + "context_category": { + "description": "ContextCategory is the context category the caller pinned on ingest:\none of user_preference, business_knowledge or decision_trace, written\nonto the source row by the ingestion pipeline (PRO-1618). Absent when no\ncategory was pinned, on rows ingested before the category existed, and\non every split-database row, which carries no category.", + "type": "string" + }, "database": { "description": "Database is the canonical name for the scope this row was listed from.", "example": "acme_corp", @@ -5588,17 +5601,29 @@ "example": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", "type": "string" }, - "name": { - "description": "Human-readable label for this resource.", - "example": "general", - "type": "string" - }, "role": { "type": "string" } }, "type": "object" }, + "memories.ForcefulRelations": { + "description": "ForcefulRelations are the contexts the caller says this one relates to,\nby context_id, with optional properties stored on each edge. They are\nfollowed at query time (follow_forceful_relations) and returned as\n`forceful_relations[]`. Any item may declare them; on the split surface\nonly a knowledge item could.", + "properties": { + "context_ids": { + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "properties": { + "additionalProperties": {}, + "type": "object" + } + }, + "type": "object" + }, "memories.IngestItem": { "properties": { "acl": { @@ -5613,11 +5638,6 @@ "additionalProperties": {}, "type": "object" }, - "content": { - "description": "Extracted text content of the source document.", - "example": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", - "type": "string" - }, "context_category": { "description": "ContextCategory files this context under one of the three buckets\n(PRO-1618). Omitted or \"auto\" leaves it to HydraDB; naming a bucket pins\nit and inference will not overwrite it. See\ndomain/ingestion/context_category.go.", "enum": [ @@ -5632,11 +5652,10 @@ "type": "string" }, "conversation": { - "description": "Conversation is the canonical name; `messages` is accepted as an alias.\nBoth are the shape a developer already builds for OpenAI or Anthropic.", + "description": "Conversation is the shape a developer already builds for OpenAI or\nAnthropic: `[{role, content}]`.", "example": [ { - "content": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", - "name": "general" + "content": "# Q4 Report\n\nRevenue grew 23% quarter over quarter." } ], "items": { @@ -5649,66 +5668,21 @@ "additionalProperties": {}, "type": "object" }, - "custom_instructions": { - "type": "string" - }, "enrich": { "example": true, "type": "boolean" }, "forceful_relations": { - "$ref": "#/components/schemas/memories.ItemRelations", - "example": { - "ids": [ - "HydraDoc1234", - "HydraDoc4567" - ], - "source_ids": [ - "HydraDoc1234", - "HydraDoc4567" - ] - } + "$ref": "#/components/schemas/memories.ForcefulRelations" }, "happened_at": { "type": "string" }, "instructions": { - "description": "Instructions steer enrichment for this item. The request-level value is\nthe default when an item names none. `custom_instructions` is accepted\nas an alias for callers that still send the memories[] name.", + "description": "Instructions steer enrichment for this item. The request-level value is\nthe default when an item names none.", "type": "string" }, - "is_markdown": { - "description": "IsMarkdown tells the pipeline the text is markdown, so it is chunked on\nstructure rather than as flat prose. The memories[] path has always\ncarried it; without it here a markdown sync has nowhere to say so.", - "example": true, - "type": "boolean" - }, - "messages": { - "example": [ - { - "content": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", - "name": "general" - } - ], - "items": { - "$ref": "#/components/schemas/memories.ConversationTurn" - }, - "type": "array", - "uniqueItems": false - }, - "relations": { - "$ref": "#/components/schemas/memories.ItemRelations", - "example": { - "ids": [ - "HydraDoc1234", - "HydraDoc4567" - ], - "source_ids": [ - "HydraDoc1234", - "HydraDoc4567" - ] - } - }, "text": { - "description": "Text is the canonical name; `content` is accepted as an alias.", "type": "string" }, "title": { @@ -5722,51 +5696,12 @@ "type": "boolean" }, "user_name": { - "description": "UserName is the speaker identity for a TEXT item. A conversation names\nits speaker per turn instead, and that stays authoritative: this field\nonly fills in when the turns supplied none. Empty ends up as \"User\",\nmatching the split path, so the pipeline is never handed a blank.", + "description": "UserName is the speaker identity for the item, on both shapes: a text\nitem is what that person said, a conversation's user turns are theirs.\nEmpty ends up as \"User\", matching the split path, so the pipeline is\nnever handed a blank.", "type": "string" } }, "type": "object" }, - "memories.ItemRelations": { - "description": "ForcefulRelations are the contexts the caller says this one relates to,\nby context_id. They are followed at query time (follow_forceful_relations)\nand returned as `relations[]`. Any item may declare them; on the split\nsurface only a knowledge item could. `relations` is accepted as an alias.", - "properties": { - "context_ids": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "ids": { - "example": [ - "HydraDoc1234", - "HydraDoc4567" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "properties": { - "additionalProperties": {}, - "type": "object" - }, - "source_ids": { - "example": [ - "HydraDoc1234", - "HydraDoc4567" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - } - }, - "type": "object" - }, "memories.UnifiedIngestRequest": { "properties": { "collection": { @@ -5775,29 +5710,14 @@ "type": "string" }, "context": { - "description": "Context is the list of contexts to ingest: the documented name. `items`\nand `contexts` are accepted as aliases because earlier drafts and the\nfirst client releases used them; they are folded onto Context before\nanything reads the request.", - "example": [ - { - "context_id": "q4_report", - "text": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", - "title": "Q4 Report" - } - ], + "description": "Context is the list of contexts to ingest.", + "example": "Ada joined Acme Corp in 2024 as a staff engineer.", "items": { "$ref": "#/components/schemas/memories.IngestItem" }, "type": "array", "uniqueItems": false }, - "contexts": { - "deprecated": true, - "items": { - "$ref": "#/components/schemas/memories.IngestItem" - }, - "type": "array", - "uniqueItems": false, - "x-deprecated": "true" - }, "database": { "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", "example": "acme_corp", @@ -5817,15 +5737,6 @@ "instructions": { "type": "string" }, - "items": { - "deprecated": true, - "items": { - "$ref": "#/components/schemas/memories.IngestItem" - }, - "type": "array", - "uniqueItems": false, - "x-deprecated": "true" - }, "upsert": { "description": "Upsert, Enrich and Instructions are the request-level defaults for the\nitem-level fields of the same name: true, true and \"\" when absent.", "example": "true", @@ -10783,7 +10694,7 @@ }, "/context/ingest": { "post": { - "description": "Ingest content for a database. `context` is the list of contexts to ingest (text or a conversation per item), the preferred shape; the deprecated `documents`, `app_knowledge` and `memories` fields are selected by `type`. `items` is a deprecated alias of `context`, accepted for SDK releases that still send it. The same `context` array may also be sent as an application/json body.", + "description": "Ingest content for a database. `context` is the preferred shape (text or a conversation per item); the deprecated `documents`, `app_knowledge` and `memories` fields are selected by `type`. The same `context` array may also be sent as an application/json body.", "requestBody": { "content": { "multipart/form-data": { @@ -10801,7 +10712,7 @@ "type": "string" }, "context": { - "description": "JSON-encoded array of contexts to ingest -- text or a conversation per item. The same array may also be POSTed as an application/json body under `context`; that variant is not listed here so SDK generators emit this form, which carries every field.", + "description": "JSON-encoded array of contexts -- text or a conversation per item. The same array may also be POSTed as an application/json body under `context`; that variant is not listed here so SDK generators emit this form, which carries every field.", "title": "context", "type": "string" }, @@ -10839,13 +10750,6 @@ "title": "instructions", "type": "string" }, - "items": { - "deprecated": true, - "description": "Deprecated alias for `context`, accepted so SDK releases that send `items` keep working. Send `context` instead; a request carrying both is rejected with 400.", - "title": "items", - "type": "string", - "x-deprecated": "true" - }, "memories": { "deprecated": true, "description": "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. Items may also carry `evidence_kind`/`evidence_subject` provenance labels (see document_metadata); an unknown kind returns 400.", @@ -10886,7 +10790,7 @@ } } }, - "description": "JSON body alternative to this form: the same fields, with the list under `context`. | Corpus to write to: 'knowledge' (default) or 'memory'. 'all' is refused here: an ingest must name the one corpus it writes to. | Contexts as a JSON array; each carries `text` or `conversation` (role/content turns), optional `context_id`, `title`, `attributes`, `custom_attributes`, `happened_at`, `enrich`, `upsert`, `instructions`, `forceful_relations`, `context_category` (auto|user_preference|business_knowledge|decision_trace), and `acl` (principal list, PRO-1684: same contract as an app_knowledge item; omit for unrestricted). Contexts land in the memory corpus. | Deprecated alias for `context`, accepted for SDK releases that still send it. Send `context`; sending both is a 400. | 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) | Deprecated: knowledge files to ingest (repeatable; type=knowledge, split databases only) | Deprecated: per-document metadata as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Deprecated: app-knowledge items as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB, optional acl principal list (PRO-1684). | Deprecated: memory items as a JSON array (type=memory, split databases only); use context. Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Request-level enrichment default for `context` (true/false/1/0) | Request-level enrichment instructions default for `context` | Optional bring-your-own-graph payload as JSON, keyed by context_id (context) or source_id (split paths)", + "description": "Context[] body: the application/json alternative to this form. | Corpus to write to: 'knowledge' (default) or 'memory'. 'all' is refused here: an ingest must name the one corpus it writes to. | 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) | Deprecated: knowledge files to ingest (repeatable; type=knowledge, split databases only) | Deprecated: per-document metadata as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Deprecated: app-knowledge items as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB, optional acl principal list (PRO-1684). | Deprecated: memory items as a JSON array (type=memory, split databases only); use context. Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Contexts as a JSON array, the same list a JSON body carries under `context`. Each is one of text | conversation ([{role, content}]), with optional context_id, title (\u003c= 1024 bytes), user_name, enrich, upsert, instructions (\u003c= 4000 chars), happened_at, attributes, custom_attributes, context_category (auto|user_preference|business_knowledge|decision_trace), forceful_relations ({context_ids, properties}), acl. At most 100 contexts, 1 MiB of text per context and 8 MiB per request. Unknown keys are refused. Contexts land in the memory corpus. | Request-level enrichment default for `context` (true/false/1/0) | Request-level enrichment instructions default for `context` (\u003c= 4000 chars) | Optional bring-your-own-graph payload as JSON, keyed by context_id (context) or source_id (split paths)", "required": true }, "responses": { diff --git a/api-reference/v2/sdks.mdx b/api-reference/v2/sdks.mdx index c0dcf238..ed7b42cc 100644 --- a/api-reference/v2/sdks.mdx +++ b/api-reference/v2/sdks.mdx @@ -179,7 +179,7 @@ while (true) { ### Ingest context -Everything you ingest is a context item: one `text` or one `conversation`, with optional fields such as `context_id`, `title`, `happened_at` and `attributes`. The SDK sends a multipart form and puts the item list, as a JSON string, in the `context` field. SDK releases generated from the current API spec take `context`; older releases take `items`, which the API still accepts as a deprecated alias. +Everything you ingest is a context item: one `text` or one `conversation`, with optional fields such as `context_id`, `title`, `happened_at` and `attributes`. The SDK sends a multipart form and puts the item list, as a JSON string, in the `context` field. ```python Python SDK @@ -197,8 +197,9 @@ result = client.context.ingest( }, { "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, + {"role": "user", "content": "Keep answers short, I read on my phone."}, {"role": "assistant", "content": "Got it, short answers."}, ], "happened_at": "2026-09-01", @@ -221,8 +222,9 @@ const result = await client.context.ingest({ }, { context_id: "chat-alex-001", + user_name: "alex", conversation: [ - { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, + { role: "user", content: "Keep answers short, I read on my phone." }, { role: "assistant", content: "Got it, short answers." }, ], happened_at: "2026-09-01", diff --git a/essentials/v2/context-categories.mdx b/essentials/v2/context-categories.mdx index a2044d80..0b8cc88a 100644 --- a/essentials/v2/context-categories.mdx +++ b/essentials/v2/context-categories.mdx @@ -44,8 +44,9 @@ curl -X POST 'https://api.hydradb.com/context/ingest' \ "collection": "user_alex", "context": [{ "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - { "role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex" }, + { "role": "user", "content": "Keep answers short, I read on my phone." }, { "role": "assistant", "content": "Got it, short answers." } ], "context_category": "user_preference", @@ -61,8 +62,9 @@ client.context.ingest( collection="user_alex", context=json.dumps([{ "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, + {"role": "user", "content": "Keep answers short, I read on my phone."}, {"role": "assistant", "content": "Got it, short answers."}, ], "context_category": "user_preference", @@ -76,8 +78,9 @@ await client.context.ingest({ collection: "user_alex", context: JSON.stringify([{ context_id: "chat-alex-001", + user_name: "alex", conversation: [ - { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, + { role: "user", content: "Keep answers short, I read on my phone." }, { role: "assistant", content: "Got it, short answers." }, ], context_category: "user_preference", diff --git a/essentials/v2/ingest.mdx b/essentials/v2/ingest.mdx index c2d1c033..4e9ed983 100644 --- a/essentials/v2/ingest.mdx +++ b/essentials/v2/ingest.mdx @@ -30,8 +30,9 @@ curl -X POST 'https://api.hydradb.com/context/ingest' \ }, { "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - { "role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex" }, + { "role": "user", "content": "Keep answers short, I read on my phone." }, { "role": "assistant", "content": "Got it, short answers." } ], "happened_at": "2026-09-01" @@ -55,8 +56,9 @@ ingest = client.context.ingest( }, { "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, + {"role": "user", "content": "Keep answers short, I read on my phone."}, {"role": "assistant", "content": "Got it, short answers."}, ], "happened_at": "2026-09-01", @@ -80,8 +82,9 @@ const ingest = await client.context.ingest({ }, { context_id: "chat-alex-001", + user_name: "alex", conversation: [ - { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, + { role: "user", content: "Keep answers short, I read on my phone." }, { role: "assistant", content: "Got it, short answers." }, ], happened_at: "2026-09-01", @@ -94,7 +97,7 @@ console.log(ingest.data.results.map((r) => r.id)); -**SDK users: `context` is a JSON string.** The SDKs send a multipart form rather than a JSON body, and the array goes in the `context` form field, which is why `context` is a JSON string there. SDK releases generated from the current API spec take `context`; older releases take `items`, which the API still accepts as a deprecated alias. The SDK methods also take `database`, `collection`, `upsert` and `graph_payload`; to set `enrich` or `instructions` through an SDK, set them on each item. Both entry points run the same validation. Prefer the JSON body with `context` when you call the API directly. Keys inside each item stay `snake_case` in every language. +**SDK users: `context` is a JSON string.** The SDKs send a multipart form rather than a JSON body, and the array goes in the `context` form field, which is why `context` is a JSON string there. The SDK methods also take `database`, `collection`, `upsert`, `enrich`, `instructions` and `graph_payload`, and `enrich` and `instructions` can also be set on each item. Both entry points run the same validation. Prefer the JSON body with `context` when you call the API directly. Keys inside each item stay `snake_case` in every language. The response is `202 Accepted`: @@ -149,25 +152,25 @@ Each item is exactly one of `text` or `conversation`. | Field | Notes | | --- | --- | | `context_id` | Your id for the item. Generated when omitted (from `title`, so two items with the same text, no title and no id collide). Must not contain commas. | -| `title` | Optional readable name. Searchable with `titles` on [query](/essentials/v2/query). | +| `title` | Optional readable name. Searchable with `titles` on [query](/essentials/v2/query). At most 1,024 bytes. | | `text` | Plain text. Shape A. See [Text items](#4-text-items). | -| `conversation` | A list of `{ role, content, name? }` turns; roles are `user`, `assistant` and `system`. Shape B. See [Conversation items](#5-conversation-items). | +| `conversation` | A list of `{ role, content }` turns; roles are `user`, `assistant` and `system`. Shape B. See [Conversation items](#5-conversation-items). | | `enrich` | Extract entities, relations and preferences from this item. Default: the request's `enrich`, else `true`. | | `upsert` | Replace an existing item with the same `context_id`. Default: the request's `upsert`, else `true`. | -| `instructions` | Steer enrichment for this item. Default: the request's `instructions`. | +| `instructions` | Steer enrichment for this item. At most 4,000 characters. Default: the request's `instructions`. | | `happened_at` | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. See [Attributes](/essentials/v2/attributes). | | `custom_attributes` | Free-form fields. Not filterable. | -| `forceful_relations` | Relations you declare to other items: `{ "ids": ["chat-w1"], "properties": {} }`, where `ids` are the `context_id`s of the related items. See [Declared relations](#10-declared-relations). | +| `forceful_relations` | Relations you declare to other items: `{ "context_ids": ["chat-w1"], "properties": {} }`, where `context_ids` are the `context_id`s of the related items. See [Declared relations](#10-declared-relations). | | `acl` | Principals allowed to retrieve the item, such as `user_email:a@x.com` or `domain:acme.com`. Omit for unrestricted, `[]` for nobody. A malformed principal is a `400`. See [Restricting an item](#9-restricting-an-item). | -| `is_markdown` | Chunk `text` on its markdown structure instead of as flat prose. | -| `user_name` | The speaker for a text item. On a conversation, the per-turn `name` wins. | +| `user_name` | The speaker for the item: the author of a text item, or the person in a conversation's `user` turns. Default `"User"`. | ### Limits and unrecognised fields - At most **100 items** per request, **1 MiB** of text per item, and **8 MiB** of text per request. +- `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each item. - A validation error names the item it refers to as `context[N]`. -- An unrecognised field on an item is ignored without an error, so check spelling against the tables above. +- An unrecognised field is a `400`, on the request, on an item, on a conversation turn or inside `forceful_relations`. The error names the field and lists the accepted ones. --- @@ -180,13 +183,11 @@ A text item is a document, a note, a policy, an agent log line: anything you alr "context_id": "runbook-deploy", "title": "Deploy runbook", "text": "# Deploying\n\n1. Merge to main.\n2. Wait for the image build.\n3. Promote in ArgoCD.", - "is_markdown": true, "user_name": "platform-team" } ``` - Set `title` so the item has a readable name and so `titles` filters can find it. -- Set `is_markdown: true` for markdown, so headings and lists shape the chunks. - Set `user_name` when the text has an author the graph should attribute facts to. ### Turning files into items @@ -198,20 +199,24 @@ A text item is a document, a note, a policy, an agent log line: anything you alr ## 5. Conversation items ```json -"conversation": [ - { "role": "system", "content": "You are a support agent for Acme." }, - { "role": "user", "content": "Our invoices are late again.", "name": "sam" }, - { "role": "user", "content": "Third time this quarter." }, - { "role": "assistant", "content": "I have escalated this to Payments." } -] +{ + "context_id": "support-chat-sam-001", + "user_name": "sam", + "conversation": [ + { "role": "system", "content": "You are a support agent for Acme." }, + { "role": "user", "content": "Our invoices are late again." }, + { "role": "user", "content": "Third time this quarter." }, + { "role": "assistant", "content": "I have escalated this to Payments." } + ] +} ``` This is the message list you already build for OpenAI or Anthropic, so you can usually pass it straight through. - Roles are `user`, `assistant` and `system`. An unknown role is a `400`. -- **`system` turns are context only.** They shape enrichment but are never stored as facts. A conversation of only `system` turns is a `400`. +- **`system` turns are context only.** They are never stored as facts. When neither the item nor the request sets `instructions`, they become the item's instructions and are held to the same 4,000-character limit; otherwise they are dropped. A conversation of only `system` turns is a `400`. - **Consecutive turns with the same role are accepted** and joined. -- **`name` is optional per turn.** Set it when several people speak in one conversation, so preferences are attributed to the right person. +- **The speaker is the item's `user_name`.** A turn carries only `role` and `content`; any other key on a turn is a `400`. - An empty list, or a turn with empty `content`, is a `400`. --- @@ -281,11 +286,11 @@ Any item, text or conversation, can declare which other items it relates to: { "context_id": "linear-PRO-1169-comment-4", "text": "Agreed: ship the fix behind the existing flag.", - "forceful_relations": { "ids": ["linear-PRO-1169"], "properties": {} } + "forceful_relations": { "context_ids": ["linear-PRO-1169"], "properties": {} } } ``` -`ids` are the `context_id`s of the related items. At query time, in `thinking` mode and with `follow_forceful_relations` on (the default), a hit on one item pulls its declared relations into the response's `forceful_relations[]`, each with the `via` link that brought it in. See [Query](/essentials/v2/query#forceful_relations). +`context_ids` are the `context_id`s of the related items. `properties` is optional: a flat map of string, number or boolean values, at most 1 KiB as compact JSON, stored on every edge the item declares. The keys `id`, `created_at`, `relation_type`, `tenant_id` and `sub_tenant_id` are reserved and are a `400`. At query time, in `thinking` mode and with `follow_forceful_relations` on (the default), a hit on one item pulls its declared relations into the response's `forceful_relations[]`, each with the `via` link that brought it in. See [Query](/essentials/v2/query#forceful_relations). --- @@ -396,7 +401,7 @@ An item carries exactly one of `text` or `conversation`. Sending both, or neithe Ingest takes text only. Extract the text from the file in your application and send it as a `text` item. See [Turning files into items](#turning-files-into-items). -An unrecognised field is ignored without an error, never guessed at. Use the names in [Item fields](#3-item-fields). +An unrecognised field is a `400` that names it and lists the accepted fields; it is never ignored or guessed at. Use the names in [Item fields](#3-item-fields). Only `user`, `assistant` and `system` are accepted. Map roles like `tool` or `human` before sending. diff --git a/essentials/v2/split-databases.mdx b/essentials/v2/split-databases.mdx index ed460dd0..c2ff6c19 100644 --- a/essentials/v2/split-databases.mdx +++ b/essentials/v2/split-databases.mdx @@ -95,7 +95,7 @@ A unified database rejects `type`, `documents`, `app_knowledge` and `memories` w | `memories[]`, `app_knowledge[]`, `documents` | `context[]` | | `id` / `source_id` | `context_id` | | `text` (memory), `content.text` (app knowledge) | `text` | -| `user_assistant_pairs` | `conversation`, as `[{ role, content, name? }]` | +| `user_assistant_pairs` | `conversation`, as `[{ role, content }]` | | `infer` (default `false`) | `enrich` (default `true`) | | `custom_instructions` | `instructions`, on the item or on the request | | `observation_date` | `happened_at` | @@ -104,9 +104,9 @@ A unified database rejects `type`, `documents`, `app_knowledge` and `memories` w | `upsert` (request only) | `upsert` on the item, with the request value as the default | | `relations` (knowledge only) | `forceful_relations`, on any item | | `acl` (app sources only) | `acl`, on any item | -| `evidence_kind`, `evidence_subject`, `expiry_time`, `retain_source` | removed; a `400` on a unified database | +| `is_markdown`, `evidence_kind`, `evidence_subject`, `expiry_time`, `retain_source` | removed; a `400` on a unified database | -A few split names are accepted as aliases on a unified database: `items` and `contexts` for `context`, `custom_instructions` for `instructions`, `relations` for `forceful_relations`, and `context_ids` or `source_ids` for the `ids` key inside it. Send the item-field names; any other unknown key on an item is dropped without an error. The full item reference is on [Ingest context](/essentials/v2/ingest#3-item-fields). +No split name is accepted on a unified database: send the item-field names. An unknown key, on the request, on an item, on a conversation turn or inside `forceful_relations`, is a `400` that names the key. The full item reference is on [Ingest context](/essentials/v2/ingest#3-item-fields). --- diff --git a/get-started/v2/core-concepts.mdx b/get-started/v2/core-concepts.mdx index 5dd5ff96..332d5f83 100644 --- a/get-started/v2/core-concepts.mdx +++ b/get-started/v2/core-concepts.mdx @@ -42,8 +42,9 @@ An item is either plain `text` or a `conversation`: a document, a policy, a supp { "context_id": "refund-policy", "title": "Refund policy", "text": "Refunds are processed within 5 business days." }, { "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - { "role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex" }, + { "role": "user", "content": "Keep answers short, I read on my phone." }, { "role": "assistant", "content": "Got it, short answers." } ] } diff --git a/get-started/v2/quickstart.mdx b/get-started/v2/quickstart.mdx index 76e64e6c..67d29fee 100644 --- a/get-started/v2/quickstart.mdx +++ b/get-started/v2/quickstart.mdx @@ -75,8 +75,9 @@ client.context.ingest( collection="user_alex", context=json.dumps([{ "context_id": "chat-alex-001", + "user_name": "alex", "conversation": [ - {"role": "user", "content": "Keep answers short, I read on my phone.", "name": "alex"}, + {"role": "user", "content": "Keep answers short, I read on my phone."}, {"role": "assistant", "content": "Got it, short answers."}, ], "happened_at": "2026-09-01", @@ -138,8 +139,9 @@ await client.context.ingest({ collection: "user_alex", context: JSON.stringify([{ context_id: "chat-alex-001", + user_name: "alex", conversation: [ - { role: "user", content: "Keep answers short, I read on my phone.", name: "alex" }, + { role: "user", content: "Keep answers short, I read on my phone." }, { role: "assistant", content: "Got it, short answers." }, ], happened_at: "2026-09-01", From 75b2c8ff4a04b267640283fd2f32e8ed5811b138 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Wed, 23 Sep 2026 19:48:08 +0530 Subject: [PATCH 03/17] docs: method labels on every hand-written endpoint page; plain ingest intro (PRO-1618) Ten endpoint pages (Create/List Databases, Database Status and Stats, Ingest Context, List Context, Delete Context, Relations, Subgraph, Query) are written by hand rather than bound to the OpenAPI spec, so the sidebar showed them with no HTTP method. Each now declares its method and path with `api:` frontmatter, and `playground: "none"` so no parameter-less "Try it" panel appears. The Ingest guide's first section said "One call for text and conversations". It now says plainly that each item is a text or a conversation, never both, and one request can carry both kinds. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/v2/endpoint/create-tenant.mdx | 2 ++ api-reference/v2/endpoint/delete-source.mdx | 2 ++ api-reference/v2/endpoint/ingest-context.mdx | 2 ++ api-reference/v2/endpoint/list-documents.mdx | 2 ++ api-reference/v2/endpoint/list-tenants.mdx | 2 ++ api-reference/v2/endpoint/query.mdx | 2 ++ api-reference/v2/endpoint/source-relations.mdx | 2 ++ api-reference/v2/endpoint/subgraph.mdx | 2 ++ api-reference/v2/endpoint/tenant-stats.mdx | 2 ++ api-reference/v2/endpoint/tenant-status.mdx | 2 ++ essentials/v2/context-categories.mdx | 2 +- essentials/v2/ingest.mdx | 4 ++-- 12 files changed, 23 insertions(+), 3 deletions(-) diff --git a/api-reference/v2/endpoint/create-tenant.mdx b/api-reference/v2/endpoint/create-tenant.mdx index b36d508e..557b9e04 100644 --- a/api-reference/v2/endpoint/create-tenant.mdx +++ b/api-reference/v2/endpoint/create-tenant.mdx @@ -1,5 +1,7 @@ --- title: "Create Database" +api: "POST https://api.hydradb.com/databases" +playground: "none" description: "Creates a space for storing context. " --- diff --git a/api-reference/v2/endpoint/delete-source.mdx b/api-reference/v2/endpoint/delete-source.mdx index c1f38b7a..9b6989cc 100644 --- a/api-reference/v2/endpoint/delete-source.mdx +++ b/api-reference/v2/endpoint/delete-source.mdx @@ -1,5 +1,7 @@ --- title: "Delete Context" +api: "DELETE https://api.hydradb.com/context" +playground: "none" description: "Delete context items by their IDs." --- diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index 59cb6231..c0dce974 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -1,5 +1,7 @@ --- title: "Ingest Context" +api: "POST https://api.hydradb.com/context/ingest" +playground: "none" description: "Send text and conversations to a database as context items." --- diff --git a/api-reference/v2/endpoint/list-documents.mdx b/api-reference/v2/endpoint/list-documents.mdx index 19f0687d..3a108cab 100644 --- a/api-reference/v2/endpoint/list-documents.mdx +++ b/api-reference/v2/endpoint/list-documents.mdx @@ -1,5 +1,7 @@ --- title: "List Context" +api: "POST https://api.hydradb.com/context/list" +playground: "none" description: "Browse the context items in a database or collection with optional filters. Results are paginated. " --- diff --git a/api-reference/v2/endpoint/list-tenants.mdx b/api-reference/v2/endpoint/list-tenants.mdx index b46fb68d..e185c036 100644 --- a/api-reference/v2/endpoint/list-tenants.mdx +++ b/api-reference/v2/endpoint/list-tenants.mdx @@ -1,5 +1,7 @@ --- title: "List Databases" +api: "GET https://api.hydradb.com/databases" +playground: "none" description: "List all databases created. " --- diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index 316a3449..dee4bc3d 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -1,5 +1,7 @@ --- title: "Query" +api: "POST https://api.hydradb.com/query" +playground: "none" description: "Retrieve ranked chunks, graph paths, forceful relations and a prompt-ready string from a database in one call." --- diff --git a/api-reference/v2/endpoint/source-relations.mdx b/api-reference/v2/endpoint/source-relations.mdx index 6be2a556..cffd12ea 100644 --- a/api-reference/v2/endpoint/source-relations.mdx +++ b/api-reference/v2/endpoint/source-relations.mdx @@ -1,5 +1,7 @@ --- title: "Inspecting Context Relations" +api: "GET https://api.hydradb.com/context/relations" +playground: "none" description: "See and explore relationships that create the brain for your AI. " --- diff --git a/api-reference/v2/endpoint/subgraph.mdx b/api-reference/v2/endpoint/subgraph.mdx index c72b44ca..c4abd2c9 100644 --- a/api-reference/v2/endpoint/subgraph.mdx +++ b/api-reference/v2/endpoint/subgraph.mdx @@ -1,5 +1,7 @@ --- title: "Connected Subgraph" +api: "GET https://api.hydradb.com/context/subgraph" +playground: "none" description: "Everything connected to one item: its thread, its replies, its parents and children, and the items it links to." --- diff --git a/api-reference/v2/endpoint/tenant-stats.mdx b/api-reference/v2/endpoint/tenant-stats.mdx index 41ee5e00..3759de2b 100644 --- a/api-reference/v2/endpoint/tenant-stats.mdx +++ b/api-reference/v2/endpoint/tenant-stats.mdx @@ -1,6 +1,8 @@ --- title: "Database Stats" +api: "GET https://api.hydradb.com/databases/stats" +playground: "none" description: "Retrieve usage statistics for a database." --- diff --git a/api-reference/v2/endpoint/tenant-status.mdx b/api-reference/v2/endpoint/tenant-status.mdx index f8ea9f52..9bb73942 100644 --- a/api-reference/v2/endpoint/tenant-status.mdx +++ b/api-reference/v2/endpoint/tenant-status.mdx @@ -1,5 +1,7 @@ --- title: "Database Status" +api: "GET https://api.hydradb.com/databases/status" +playground: "none" description: "Check the readiness of a database's infrastructure." --- diff --git a/essentials/v2/context-categories.mdx b/essentials/v2/context-categories.mdx index 0b8cc88a..613ae1bb 100644 --- a/essentials/v2/context-categories.mdx +++ b/essentials/v2/context-categories.mdx @@ -90,7 +90,7 @@ await client.context.ingest({ ``` -The SDKs send the same `context` array as a JSON string in a form field of the same name. See [Ingest context](/essentials/v2/ingest#1-one-call-for-text-and-conversations). +The SDKs send the same `context` array as a JSON string in a form field of the same name. See [Ingest context](/essentials/v2/ingest#1-send-context). Recall it by querying that person's collection: diff --git a/essentials/v2/ingest.mdx b/essentials/v2/ingest.mdx index 4e9ed983..e2736896 100644 --- a/essentials/v2/ingest.mdx +++ b/essentials/v2/ingest.mdx @@ -7,9 +7,9 @@ Everything you put into HydraDB is a piece of **context**: a text, or a conversa --- -## 1. One call for text and conversations +## 1. Send context -A single request can mix text and conversation items, in any collection of a database. The body is JSON, and the list is called `context`. +`POST /context/ingest` takes a list called `context`. Each item is either a `text` or a `conversation`, never both. One request can carry items of both kinds. ```bash cURL From 5df5fae5bf69fdd4ff03258185d17be75638e704 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Wed, 23 Sep 2026 20:08:19 +0530 Subject: [PATCH 04/17] docs: final review of every visible page against app staging (PRO-1618) Checked each page in docs.json navigation against the Go code on hydradb-application origin/staging and fixed what was untrue or padded. Truth fixes: - /query: max_results maximum is 250 (was 50); mode "auto" does not override graph_context (claims removed); alpha "auto" resolves to 0.8 (advice to use it removed); recency_bias defaults to 0.4; query_apps defaults to true; relation.timestamp examples are whole numbers (Go never prints 1782984600.0) and mean when the relation was introduced. - The shared thinking-mode example: its query path now has no relevance and a narrated path_summary, as thinking-mode query paths do. - Ingest: an omitted context_id is generated from the item's text and title, not the title alone; the quickstart and AGENTS cURL samples put the speaker in item-level user_name instead of a per-turn name (a 400). - Databases: names are up to 255 chars of [a-z0-9_-]; re-creating a failed database needs a delete first; dense/sparse fields cannot be added after creation; real error and success strings throughout. - Connectors, webhooks, BYOG graph collections, access control, status, inspect, list, relations and feedback pages: fields, codes and examples that the server does not produce were corrected. Cleanup: "lane" jargon, filler, repeated paragraphs, spaced hyphens used as dashes, stale Multi-tenant/Metadata link labels, invalid JSON blocks. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 50 +++--- .../v2/endpoint/add-connector-resource.mdx | 13 +- .../v2/endpoint/configure-connector.mdx | 28 +-- .../v2/endpoint/connector-resources.mdx | 14 +- .../v2/endpoint/connectors-overview.mdx | 30 ++-- .../v2/endpoint/create-connector.mdx | 28 +-- api-reference/v2/endpoint/create-tenant.mdx | 14 +- .../v2/endpoint/delete-collection.mdx | 12 +- .../v2/endpoint/delete-connector-resource.mdx | 4 +- .../v2/endpoint/delete-connector.mdx | 6 +- api-reference/v2/endpoint/delete-source.mdx | 16 +- api-reference/v2/endpoint/delete-tenant.mdx | 20 +-- .../endpoint/discover-connector-resources.mdx | 11 +- api-reference/v2/endpoint/fetch-content.mdx | 19 +- .../v2/endpoint/get-connector-provider.mdx | 2 + api-reference/v2/endpoint/get-connector.mdx | 13 +- .../v2/endpoint/list-connector-providers.mdx | 13 +- api-reference/v2/endpoint/list-connectors.mdx | 10 +- api-reference/v2/endpoint/list-documents.mdx | 12 +- .../v2/endpoint/list-sub-tenants.mdx | 16 +- api-reference/v2/endpoint/list-tenants.mdx | 8 +- .../v2/endpoint/list-webhook-deliveries.mdx | 4 +- api-reference/v2/endpoint/query-overview.mdx | 10 +- api-reference/v2/endpoint/query.mdx | 42 ++--- .../v2/endpoint/retry-webhook-delivery.mdx | 2 +- .../v2/endpoint/source-relations.mdx | 37 ++-- api-reference/v2/endpoint/source-status.mdx | 33 ++-- .../v2/endpoint/sources-overview.mdx | 4 +- api-reference/v2/endpoint/subgraph.mdx | 22 +-- api-reference/v2/endpoint/submit-feedback.mdx | 54 +++--- api-reference/v2/endpoint/sync-connector.mdx | 8 +- api-reference/v2/endpoint/tenant-stats.mdx | 6 +- api-reference/v2/endpoint/tenant-status.mdx | 10 +- .../v2/endpoint/tenants-overview.mdx | 11 +- .../v2/endpoint/update-metadata-schema.mdx | 51 +++--- .../v2/endpoint/update-source-metadata.mdx | 45 +++-- api-reference/v2/error-responses.mdx | 20 +-- api-reference/v2/index.mdx | 6 +- api-reference/v2/sdks.mdx | 14 +- continuity-assurance.mdx | 12 +- essentials/v2/access-control.mdx | 30 ++-- essentials/v2/api-results.mdx | 5 +- essentials/v2/architecture.mdx | 32 ++-- essentials/v2/attributes.mdx | 12 +- essentials/v2/bring-your-own-graph.mdx | 6 +- essentials/v2/connectors.mdx | 80 ++++----- essentials/v2/context-graphs.mdx | 13 +- essentials/v2/databases-and-collections.mdx | 23 +-- essentials/v2/glossary.mdx | 12 +- essentials/v2/graph-collections-byog.mdx | 165 +++++++++--------- essentials/v2/ingest.mdx | 10 +- essentials/v2/query.mdx | 36 ++-- essentials/v2/semantic-search.mdx | 20 +-- essentials/v2/webhooks.mdx | 15 +- get-started/v2/core-concepts.mdx | 4 +- get-started/v2/introduction.mdx | 10 +- get-started/v2/quickstart.mdx | 7 +- plugins/claude-code.mdx | 32 ++-- plugins/cli.mdx | 16 +- plugins/mcp.mdx | 56 +++--- plugins/openclaw.mdx | 14 +- 61 files changed, 670 insertions(+), 658 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index 82b8c878..82248b0d 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -5,7 +5,7 @@ description: "LLM-facing reference for building against the current HydraDB API # HydraDB Agent Integration Guide -This document is a self-contained reference for AI coding agents. It covers everything needed to understand, install, configure and integrate HydraDB into a project, from zero prior knowledge to production use. +This document is a self-contained reference for AI coding agents integrating HydraDB into a project. HydraDB stores **context**: text and conversations that you send as items. It chunks, embeds and enriches every item, extracts entities and relations into a context graph, and answers questions over all of it through one query endpoint that returns ranked chunks, graph paths and a prompt-ready string. @@ -42,6 +42,7 @@ Core raw HTTP responses (`/databases`, `/context/*`, `/query` and `/feedback`) a "error": null, "meta": { "request_id": "...", + "api_version": "2.0.1", "latency_ms": 12.3 } } @@ -177,7 +178,7 @@ except Exception: Send `answer`, `source_ids`, or both. Do **not** guess: only send ground truth you actually have. A fabricated answer key is worse than none, because it is scored as if it were true. -Limit: 100 submissions per minute per organization, far above what reporting only the queries that fell short will ever reach. If you do hit `429`, honour `Retry-After` or simply skip that report. Never spin. +Limit: 100 submissions per minute per organization, far above what reporting only the queries that fell short will ever reach. If you do hit `429`, honour `Retry-After` or skip that report. Never spin. ### Do not mix scopes accidentally @@ -314,7 +315,7 @@ const client = new HydraDBClient({ SDK naming: - Python methods and fields: snake_case, for example `client.databases.collections()`, `max_results`, `query_by`, `result.data.llm_prompt`, `status.indexing_status`. -- TypeScript methods and fields: camelCase, for example `maxResults`, `queryBy`, `pageSize`, `result.data.llmPrompt`, `chunk.chunkId`, `chunk.contextId`, `chunk.enrichmentKind`, `path.pathSummary`, `result.data.forcefulRelations`, `status.indexingStatus`. +- TypeScript methods and fields: camelCase, for example `maxResults`, `queryBy`, `pageSize`, `result.data.llmPrompt`, `chunk.chunkId`, `chunk.contextId`, `path.pathSummary`, `result.data.forcefulRelations`, `status.indexingStatus`. - Both SDKs return a `{ success, data, error, meta }` envelope; the payload is under `.data` (for example `response.data.infra`, `response.data.statuses`, `response.data.results`). - `client.context.ingest()` sends a multipart form: the item list goes in the `context` form field as a JSON string. Keys inside each item stay snake_case in every language (`context_id`, `happened_at`, `custom_attributes`), because that string is raw wire data. @@ -585,7 +586,7 @@ Notes: - `database` is the only required field. `database_metadata_schema` is optional and declares the filterable `attributes` (see [Attributes guide](#10-attributes-guide)). - Database creation is asynchronous. -- `database` is a stable, case-sensitive id of up to 25 characters, immutable after creation. Lowercase letters, numbers and underscores are the most portable. +- `database` is an id of up to 255 characters: lowercase letters, digits, `-` and `_` only; anything else is a `400`. `PATCH /databases/{database}` renames it, and the old name stops resolving immediately. - Plan the schema up front. You can add fields later with `PATCH /databases/{database}/metadata-schema` (additive only: no deletes, no data-type or flag changes). - `POST /databases` may return `409 DATABASE_ALREADY_EXISTS` for a duplicate name and `403 FORBIDDEN` when the API key or plan cannot create more databases. @@ -666,7 +667,7 @@ Each item carries exactly one of `text` or `conversation`. | Field | Notes | |---|---| -| `context_id` | Your id for the item. Generated from `title` when omitted, so two untitled items without ids and with the same text collide. Must not contain commas. | +| `context_id` | Your id for the item. When omitted it is generated from the item's text and `title`, so two items without ids that have the same text and the same (or no) title collide. Must not contain commas. | | `title` | Optional readable name, printed in `llm_prompt` and matchable with `titles` on `/query`. At most 1,024 bytes. | | `text` | Plain text or markdown. | | `conversation` | A list of `{ role, content }` turns. | @@ -811,12 +812,12 @@ Payload shape (`id` is the item's `context_id`): "database": "", "collection": "", "status": "completed", - "timestamp": "", - "error_code": null, - "error_message": null + "timestamp": "" } ``` +An `errored` event can also carry `error_code` and `error_message`; each key is present only when it has a value. + Headers: - `X-HydraDB-Delivery-ID` @@ -861,17 +862,17 @@ Rules: | `collection` | string | Search one collection; the default collection when neither this nor `collections` is sent. | | `collections` | `string[]` or `{ [collection]: weight }` | Search several. A list weights them equally; an object ranks one above another (weights rank, they do not exclude). Maximum 100. | | `query` | string | Required. The question or search terms. | -| `max_results` | integer | Default `10`, maximum `50`. Caps the merged result across collections. | +| `max_results` | integer | Default `10`, maximum `250`. Caps the merged result across collections. | | `mode` | `auto`, `fast`, `thinking` | `auto` (default) routes each query. `thinking` expands the query, reranks, traverses the graph further and follows forceful relations; `fast` is one pass. | | `query_by` | `hybrid`, `text` | `hybrid` (default) blends semantic and BM25; `text` is BM25 only. | | `operator` | `or`, `and`, `phrase` | BM25 term matching; only for `query_by: "text"`. Default `or`. | -| `alpha` | `0.0` to `1.0`, or `"auto"` | Semantic versus keyword blend for `hybrid`. `1.0` is fully semantic, `0.0` fully BM25. Default `0.8`. | -| `recency_bias` | `0.0` to `1.0` | Boost newer content. `0` disables it. | +| `alpha` | `0.0` to `1.0`, or `"auto"` | Semantic versus keyword blend for `hybrid`. `1.0` is fully semantic, `0.0` fully BM25. Default `0.8`; `"auto"` also resolves to `0.8`. | +| `recency_bias` | `0.0` to `1.0` | Boost newer content. Default `0.4`; `0` disables it. | | `ids` | `string[]` | Restrict retrieval to these `context_id`s. | | `titles` | `string[]` | Restrict retrieval to items with one of these exact titles (case-insensitive). | | `attributes` | object | Filter on declared attributes with operators. See [Filtering with attributes](#filtering-with-attributes). | | `acl` | `string[]` | Query on behalf of an identity: only items it may retrieve are returned. Omitted, empty or `["*"]` disables filtering. | -| `query_apps` | boolean | App-aware lane for connector content (exact ids, actors, threads), on top of normal retrieval. | +| `query_apps` | boolean | Default `true`: also search connector content by its app identity (exact ids, actors, threads), on top of normal retrieval. | | `graph_context` | boolean | Default `true`: include `graph[]`. | | `follow_forceful_relations` | boolean | Default `true`: pull declared forceful relations into `forceful_relations[]` (`thinking` mode only). | | `temporal_reasoning` | boolean | Default `true`. Resolve time-based questions (current, as of, ranges); matched facts come back in `chunks[].temporal`. Never changes which chunks are returned. | @@ -883,13 +884,13 @@ Rules: | Goal | Request shape | |---|---| | Fast RAG | `mode: "fast"`, `graph_context: false`, `max_results: 5` to `10` | -| Highest-quality RAG | `mode: "thinking"`, `alpha: "auto"` (graph on by default) | +| Highest-quality RAG | `mode: "thinking"` (graph on by default) | | Personalized grounded answer | `collections: { "user_alex": 2, "company": 1 }`, `mode: "thinking"` | | One person's context only | `collection: "user_alex"` | | Exact keyword or phrase | `query_by: "text"`, `operator: "phrase"` | | Error codes, SKUs, product names | `query_by: "hybrid"`, `alpha: 0.3` to `0.5` | -| Recent operational updates | `recency_bias: 0.2` to `0.4`, plus an `attributes` filter on status or doc type | -| Connector content (Slack, Jira, Gmail) | `query_apps: true`, `mode: "thinking"` | +| Recent operational updates | `recency_bias` above the default `0.4`, plus an `attributes` filter on status or doc type | +| Connector content (Slack, Jira, Gmail) | `mode: "thinking"` (`query_apps` is on by default) | | Follow linked items | `mode: "thinking"` (`follow_forceful_relations` is on by default) | | A known item or document | `ids: [...]` or `titles: [...]` | @@ -966,7 +967,7 @@ Attribute-filtered search, on behalf of one user: "target": { "entity_id": "ent_finance", "name": "Finance Department" } } ], - "path_summary": "Refund processing is managed by the Finance Department." + "path_summary": "Refund Processing managed by Finance Department." }, { "origin": "chunk_relation", @@ -1007,7 +1008,7 @@ Attribute-filtered search, on behalf of one user: Chunks carry nothing about their source: no title, url, collection or attributes. `llm_prompt` prints the title, collection, last-updated date and url for the model. To read an item's stored content yourself, call `GET /context/inspect` with the chunk's `context_id`; to read its title and attributes, call `POST /context/list` with `ids: [context_id]`. -`graph[]`: paths through the context graph, query paths first, then paths expanded from the returned chunks. The list is deduplicated across both lanes (a path both found is reported once, as a `query_path`) and is not capped. `[]` when `graph_context` is `false` or nothing connects. +`graph[]`: paths through the context graph, query paths first, then paths expanded from the returned chunks. The list is deduplicated across both origins (a path found both ways is reported once, as a `query_path`) and is not capped. `[]` when `graph_context` is `false` or nothing connects. | Field | Meaning | |---|---| @@ -1016,7 +1017,7 @@ Chunks carry nothing about their source: no title, url, collection or attributes | `triplets[].relation.predicate` | The relation, for example `managed by`. | | `triplets[].relation.context` | The sentence the relation was extracted from. | | `triplets[].relation.temporal_details` | When the relation holds, for example `since June`. Omitted when empty. | -| `triplets[].relation.timestamp` | Epoch seconds (a float) for the edge. Omitted when the edge has none. | +| `triplets[].relation.timestamp` | When the relation was introduced: the date of the source it was extracted from, in epoch seconds (may be fractional). Omitted when the edge has none. | | `triplets[].relation.relationship_id` | The relation's id. | | `triplets[].relation.chunk_id` | The chunk the relation was extracted from. | | `path_summary` | One sentence summarizing the path. Never empty. | @@ -1107,11 +1108,14 @@ FAQ: refunds to a card take 5 to 7 business days to appear. ## Related facts -- [P1] **Refund Processing** -managed by→ **Finance Department** (relevance 0.81) [1] - Refund processing is managed by the Finance Department. +- [P1] **Refund Processing** -managed by→ **Finance Department** [1] - [P2] **User** -prefers→ **short answers** (relevance 0.74) [2] The user prefers short answers about refunds. +## Temporal facts + +- **Refund policy** *effective_from* → **June 2026** (from 2026-06-01, precision: month, status: ongoing; evidence: "from June") [1] + ## Sources 1. **Refund policy** (file, id: refund-policy) · https://docs.acme.com/refunds · updated 2026-07-02 @@ -1208,7 +1212,7 @@ Declared at `POST /databases` (or added later with `PATCH /databases/{database}/ | Field | Purpose | |---|---| -| `name` | Attribute key. Starts with a letter or `_`; letters, numbers and underscores only; not a reserved system name such as `chunk_id`. | +| `name` | Attribute key. Starts with a letter; letters, numbers and underscores only; not a reserved system name such as `chunk_id`. | | `data_type` | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the aliases `string`, `boolean`, `integer`, `float`, `object`. Default `VARCHAR`. Arrays are not supported. | | `enable_match` | Fast exact-match path for a field you filter on often. | | `enable_dense_embedding` | Semantic search over a `VARCHAR` field. | @@ -1293,7 +1297,7 @@ Parameters: - `database`, `collection` - `ids`: only these `context_id`s (filters and paging still apply) - `page` (1-indexed, default `1`), `page_size` (`1` to `100`, default `50`) -- `filters`: exact-match constraints, ANDed. `source_fields` matches built-in fields such as `title`, `url`, `timestamp` and, for connector content, `app_provider`, `app_kind`, `app_external_id`, `app_parent_id`. The list filter keeps its own wire names for the two attribute maps: `filters.metadata` matches declared `attributes` and `filters.additional_metadata` matches `custom_attributes`. +- `filters`: exact-match constraints, ANDed (`source_fields.title` matches a case-insensitive prefix). `source_fields` matches built-in fields such as `title`, `url`, `timestamp` and, for connector content, `app_provider`, `app_kind`, `app_external_id`, `app_parent_id`. The list filter keeps its own wire names for the two attribute maps: `filters.metadata` matches declared `attributes` and `filters.additional_metadata` matches `custom_attributes`. - `include_fields`: projection, for example `["title", "timestamp"]`. `content` and `url` are not projectable (a `400`); read an item's content with `GET /context/inspect`. - `acl`: list as an identity; only items it may see are returned. @@ -1403,7 +1407,7 @@ Response `data`: `results[]` (per id: `id`, `deleted`, `error`), `deleted_count` ### Ingest validation (`400`) -The whole request is rejected, and the message names the item as `context[N]`, when an item has both `text` and `conversation` (or neither), a conversation breaks the rules in [Conversations](#conversations), `happened_at` is malformed, an `acl` principal is malformed, a `graph_payload` key matches no item, or a size limit in [Limits and validation](#limits-and-validation) is exceeded. +The whole request is rejected, and the message names the item as `context[N]`, when an item has both `text` and `conversation` (or neither), a conversation breaks the rules in [Conversations](#conversations), `happened_at` is malformed, an `acl` principal is malformed, or a size limit in [Limits and validation](#limits-and-validation) is exceeded. A `graph_payload` key that matches no item is also a `400`, and the message names the key. Retry only `429`, `500`, `502` and `503`; use bounded exponential backoff with jitter. diff --git a/api-reference/v2/endpoint/add-connector-resource.mdx b/api-reference/v2/endpoint/add-connector-resource.mdx index b1f241ba..58676d75 100644 --- a/api-reference/v2/endpoint/add-connector-resource.mdx +++ b/api-reference/v2/endpoint/add-connector-resource.mdx @@ -19,7 +19,7 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/resources' \ "resource_id": "{resource_id}", "resource_type": "channel", "display_name": "general", - "sub_tenant_id_override": "all-hands" + "collection_override": "all-hands" }' ``` @@ -38,7 +38,8 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/resources' \ | | Resource identifier from `GET /connectors/:id/discover`. | | | Resource type from `GET /connectors/:id/discover`. | | | Human-readable name for this resource. | -| | Routes synced objects from this resource into a specific sub-tenant partition. | +| | Routes synced objects from this resource into a specific collection. (deprecated alias: `sub_tenant_id_override`) | +| | Routes synced objects from this resource into a different database. (deprecated alias: `tenant_id_override`) | @@ -52,6 +53,8 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/resources' \ "provider_cursor": "", "tenant_id_override": "", "sub_tenant_id_override": "all-hands", + "database_override": "", + "collection_override": "all-hands", "provider_metadata": null, "filters": null } @@ -63,6 +66,6 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/resources' \ ## Related Resources -- [List Connector Resources](/api-reference/v2/endpoint/connector-resources) - view all resources and sync state -- [Delete Connector Resource](/api-reference/v2/endpoint/delete-connector-resource) - remove a resource -- [Configure Connector](/api-reference/v2/endpoint/configure-connector) - add multiple resources with metadata and lookback settings +- [List Connector Resources](/api-reference/v2/endpoint/connector-resources): view all resources and sync state +- [Delete Connector Resource](/api-reference/v2/endpoint/delete-connector-resource): remove a resource +- [Configure Connector](/api-reference/v2/endpoint/configure-connector): add multiple resources with metadata and lookback settings diff --git a/api-reference/v2/endpoint/configure-connector.mdx b/api-reference/v2/endpoint/configure-connector.mdx index 9eafca73..081357e7 100644 --- a/api-reference/v2/endpoint/configure-connector.mdx +++ b/api-reference/v2/endpoint/configure-connector.mdx @@ -6,7 +6,7 @@ openapi: "api-reference/v2/openapi.json POST /connectors/{id}/configure" import { Field } from "/snippets/field.jsx"; -Activates one or more resources and sets sync options. This is the step between discovery and the first sync. You can call configure again at any time to add resources, change `sub_tenant_id`, or update metadata - the cursor is preserved on reconfigure. +Activates one or more resources and sets sync options, then starts a sync right away unless the connector is paused. You can call configure again at any time to add resources, change `collection`, or update metadata; the cursor is preserved on reconfigure. @@ -22,7 +22,7 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/configure' \ "resource_id": "{resource_id}", "resource_type": "channel", "name": "general", - "sub_tenant_id": "all-hands", + "collection": "all-hands", "metadata": { "department": "all-hands" }, "additional_metadata": { "internal_label": "general-slack" } }, @@ -30,7 +30,7 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/configure' \ "resource_id": "{resource_id_2}", "resource_type": "channel", "name": "engineering", - "sub_tenant_id": "engineering", + "collection": "engineering", "metadata": { "department": "engineering" } } ] @@ -50,7 +50,7 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/configure' \ | Name | Description | | --- | --- | | | Resources to activate. Each item corresponds to one entry from [Discover](/api-reference/v2/endpoint/discover-connector-resources). | -| | How far back the first sync fetches historical data. Only applies to the initial sync - subsequent syncs are incremental from the last cursor. (default: `30`) | +| | How far back the first sync fetches historical data. Only applies to the initial sync; later syncs are incremental from the last cursor. Above `30`, some providers fetch the older history in background chunks, and the response then reports `backfill: true`. (default: `30`) | ### Resource item fields @@ -59,9 +59,9 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/configure' \ | | Resource identifier from `GET /connectors/:id/discover`. | | | Resource type from `GET /connectors/:id/discover` (e.g. `channel`, `repo`, `linear_team`). | | | Display name for this resource. | -| | Routes synced objects from this resource into a specific sub-tenant partition. Overrides the connector-level `sub_tenant_id`. | -| | Key-value pairs merged into tenant metadata on every synced object from this resource. Undeclared keys are accepted and stored, but only keys declared in `database_metadata_schema` are indexed for filtering. | -| | Key-value pairs merged into document metadata on every synced object from this resource. Free-form, no schema required. | +| | Routes synced objects from this resource into a specific collection. Overrides the connector-level `collection`. (deprecated alias: `sub_tenant_id`) | +| | Key-value pairs merged into the attributes of every synced object from this resource. Undeclared keys are accepted and stored, but only keys declared in `database_metadata_schema` are indexed for filtering. | +| | Key-value pairs merged into the custom attributes of every synced object from this resource. Free-form, no schema required. | See [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) for how these merge with system-generated fields. @@ -69,21 +69,23 @@ See [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) for ```json 200 { - "backfill": false, + "connector_id": "{connector_id}", "configured": 2, - "connector_id": "{connector_id}" + "backfill": false, + "first_sync_at": "2026-06-01T13:05:00Z", + "message": "First sync is running. Data usually appears within a few minutes; the connector reports lifecycle 'ingesting' until data has synced." } ``` -`configured` is the count of resources successfully activated. +`configured` is the count of resources successfully activated. `message` says whether the first sync started now or when the scheduled one runs. `warnings`, when present, names resources that were saved but returned nothing when probed.
## Related Resources -- **Next:** [Sync Connector](/api-reference/v2/endpoint/sync-connector) - trigger an on-demand sync (the scheduler also runs hourly automatically) -- **Next:** [Connector Resources](/api-reference/v2/endpoint/connector-resources) - poll `provider_cursor` to confirm sync ran -- [Discover Resources](/api-reference/v2/endpoint/discover-connector-resources) - find resource IDs before configuring +- **Next:** [Sync Connector](/api-reference/v2/endpoint/sync-connector): trigger another sync on demand (the scheduler runs hourly by default) +- **Next:** [List Connector Resources](/api-reference/v2/endpoint/connector-resources): poll `provider_cursor` to confirm sync ran +- [Discover Resources](/api-reference/v2/endpoint/discover-connector-resources): find resource IDs before configuring - [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) diff --git a/api-reference/v2/endpoint/connector-resources.mdx b/api-reference/v2/endpoint/connector-resources.mdx index d0e56dd1..1fd952d0 100644 --- a/api-reference/v2/endpoint/connector-resources.mdx +++ b/api-reference/v2/endpoint/connector-resources.mdx @@ -6,7 +6,7 @@ openapi: "api-reference/v2/openapi.json GET /connectors/{id}/resources" import { Field } from "/snippets/field.jsx"; -Returns all resources configured on a connector and their current sync state. Poll `provider_cursor` after triggering a sync - a non-empty value confirms the sync ran. +Returns all resources configured on a connector and their current sync state. Poll `provider_cursor` after triggering a sync: a non-empty value confirms the sync ran. @@ -38,6 +38,8 @@ curl 'https://api.hydradb.com/connectors/{id}/resources' \ "provider_cursor": "{cursor}", "tenant_id_override": "", "sub_tenant_id_override": "all-hands", + "database_override": "", + "collection_override": "all-hands", "provider_metadata": null, "filters": { "lookback_days": 30 @@ -49,13 +51,13 @@ curl 'https://api.hydradb.com/connectors/{id}/resources' \ -Use `status` and `provider_cursor` to track sync state. A non-empty `provider_cursor` confirms the first sync has run. +Use `status` and `provider_cursor` to track sync state. `database_override` and `collection_override` show where the resource's objects are routed (empty means the connector's own); `tenant_id_override` and `sub_tenant_id_override` are deprecated aliases.
## Related Resources -- [Add Connector Resource](/api-reference/v2/endpoint/add-connector-resource) - add a single resource -- [Delete Connector Resource](/api-reference/v2/endpoint/delete-connector-resource) - remove a resource -- [Configure Connector](/api-reference/v2/endpoint/configure-connector) - activate multiple resources with metadata and lookback settings -- [Sync Connector](/api-reference/v2/endpoint/sync-connector) - trigger a sync +- [Add Connector Resource](/api-reference/v2/endpoint/add-connector-resource): add a single resource +- [Delete Connector Resource](/api-reference/v2/endpoint/delete-connector-resource): remove a resource +- [Configure Connector](/api-reference/v2/endpoint/configure-connector): activate multiple resources with metadata and lookback settings +- [Sync Connector](/api-reference/v2/endpoint/sync-connector): trigger a sync diff --git a/api-reference/v2/endpoint/connectors-overview.mdx b/api-reference/v2/endpoint/connectors-overview.mdx index 3d467322..165b3c11 100644 --- a/api-reference/v2/endpoint/connectors-overview.mdx +++ b/api-reference/v2/endpoint/connectors-overview.mdx @@ -3,7 +3,7 @@ title: "Connectors - Overview" description: "Quick reference for all connector endpoints, their lifecycle, and when to call each." --- -Connectors continuously sync external app data (Slack, GitHub, Linear, Notion, Gmail) into your database without manual ingestion. +Connectors continuously sync external app data (for example Slack, GitHub, Linear, Notion or Gmail) into your database without manual ingestion. ## Endpoint references @@ -45,30 +45,30 @@ API-Version: 2 ## Key concepts -- **Connector** - An authenticated connection to one external provider account. A single connector manages all resources synced from that account. -- **Resource** - A syncable unit within a provider: a Slack channel, GitHub repo, Linear team/project, Notion database/page, or Gmail label. You activate resources individually via `/configure`. -- **Cursor** - A per-resource bookmark of the last synced position. Sync is incremental: only content newer than the cursor is fetched on each run. -- **provider_account_scope** - An identifier for the external account (e.g. Slack workspace ID, GitHub org). Used as part of the deduplication key - two connectors for the same provider must have distinct `provider_account_scope` values. +- **Connector**: an authenticated connection to one external provider account. A single connector manages all resources synced from that account. +- **Resource**: a syncable unit within a provider, such as a Slack channel, GitHub repo, Linear team or project, Notion database or page, or Gmail label. You activate resources individually via `/configure`. +- **Cursor**: a per-resource bookmark of the last synced position. Sync is incremental: only content newer than the cursor is fetched on each run. +- **provider_account_scope**: an identifier for the external account (for example a Slack workspace ID or GitHub org). It is part of every synced item's ID, so two connectors for the same provider need distinct values. ## Metadata on synced objects Every object synced by a connector has two metadata layers. -### Tenant metadata (`metadata`) +### Attributes (`metadata`) -Tenant metadata is the **schema-declared** layer. Fields are defined per tenant through `database_metadata_schema` and are indexed for fast, exact-match filtering. Use it for stable fields you filter on often, such as `department`, `region`, `status`, or `priority`. +Attributes are the **schema-declared** layer. Fields are declared once per database in `database_metadata_schema` and indexed for exact-match filtering. Use it for stable fields you filter on often, such as `department`, `region`, `status`, or `priority`. -HydraDB writes `provider` into tenant metadata for every synced object. You can add fields through `metadata` on each resource in [Configure Connector](/api-reference/v2/endpoint/configure-connector). User-supplied fields are merged first; `provider` takes precedence. +HydraDB writes `provider` and `connector_id` into the attributes of every synced object. You can add fields through `metadata` on each resource in [Configure Connector](/api-reference/v2/endpoint/configure-connector). User-supplied fields are merged first; `provider` and `connector_id` take precedence. -### Document metadata (`additional_metadata`) +### Custom attributes (`additional_metadata`) -Document metadata is the **free-form** layer and needs no schema. Connectors automatically populate provider-specific fields including connector ID, resource ID, provider account scope, and provider-native identifiers. +Custom attributes are the **free-form** layer and need no schema. Connectors automatically populate provider-specific fields including connector ID, resource ID, provider account scope, and provider-native identifiers. You can add fields through `additional_metadata` on each resource in [Configure Connector](/api-reference/v2/endpoint/configure-connector). User-supplied fields are merged first; provider-generated fields take precedence. -Use document metadata to scope a query to a connector, channel, repository, or inbox: +Use custom attributes to scope a query to a connector, channel, repository, or inbox. [`attributes`](/essentials/v2/attributes) on `/query` does not reach them, so these filters use `metadata_filters`: -```json Querying with document metadata filter +```json Querying with a custom attribute filter { "database": "acme_corp", "query": "deployment checklist", @@ -93,13 +93,13 @@ You can create more than one connector for the same provider, such as two Slack Set a distinct `provider_account_scope` for each account. It is part of every object's deduplication key; without it, objects from two accounts of the same provider can collide. -You can also route resources from one connector to different sub-tenants with [Configure Connector](/api-reference/v2/endpoint/configure-connector): +You can also route resources from one connector to different collections with [Configure Connector](/api-reference/v2/endpoint/configure-connector): ```json { "resources": [ - { "resource_id": "C_GENERAL", "name": "general", "sub_tenant_id": "all-hands" }, - { "resource_id": "C_ENG", "name": "engineering", "sub_tenant_id": "engineering" } + { "resource_id": "C_GENERAL", "name": "general", "collection": "all-hands" }, + { "resource_id": "C_ENG", "name": "engineering", "collection": "engineering" } ] } ``` diff --git a/api-reference/v2/endpoint/create-connector.mdx b/api-reference/v2/endpoint/create-connector.mdx index 1ed7efc9..c7f1c3b8 100644 --- a/api-reference/v2/endpoint/create-connector.mdx +++ b/api-reference/v2/endpoint/create-connector.mdx @@ -33,26 +33,34 @@ curl -X POST 'https://api.hydradb.com/connectors' \ | Name | Description | | --- | --- | -| | Provider to connect. One of `slack`, `github`, `linear`, `notion`, `gmail`. | -| | Human-readable label for this connector. | +| | Provider to connect: a `provider` value from [List Connector Providers](/api-reference/v2/endpoint/list-connector-providers). | +| | Human-readable label for this connector. | | | Which database receives the synced data. (deprecated alias: `tenant_id`) | | | Default collection partition for synced objects. Individual resources can override this. (deprecated alias: `sub_tenant_id`; default: `""`) | -| | Identifier for the external account (e.g. Slack workspace ID, GitHub org name). Used in deduplication - must be distinct across connectors for the same provider. | -| | Provider-specific credentials. Typically `{ "api_token": "..." }` or `{ "access_token": "..." }`. | +| | Identifier for the external account (e.g. Slack workspace ID, GitHub org name). It is part of every synced item's ID, so use a distinct value for each connector of the same provider. | +| | Provider-specific credentials, matching the provider's `credential_schema` from [Get Connector Provider](/api-reference/v2/endpoint/get-connector-provider). Token-based providers typically take `{ "api_token": "..." }` or `{ "access_token": "..." }`. | +| | Seconds between scheduled syncs. From `300` to `604800`; a few providers set a higher minimum or a lower maximum. (default: `3600`) | +| | Instructions that steer how this connector's synced documents are ingested and indexed. Up to 4000 characters. | ```json 201 { "connector_id": "{connector_id}", - "provider": "slack", - "name": "acme-engineering", "tenant_id": "acme_corp", "sub_tenant_id": "engineering", + "database": "acme_corp", + "collection": "engineering", + "name": "acme-engineering", + "provider": "slack", "provider_account_scope": "T12345ACME", + "auth_type": "", "status": "active", - "next_sync_at": "2026-06-01T13:00:00Z", - "sync_interval_seconds": 3600 + "next_sync_at": "2026-06-01T13:05:00Z", + "sync_interval_seconds": 3600, + "lifecycle": "active", + "first_sync_at": "2026-06-01T13:05:00Z", + "message": "Connector created. Configure resources to start syncing; the first scheduled sync runs in about 5 minutes." } ``` @@ -62,7 +70,7 @@ curl -X POST 'https://api.hydradb.com/connectors' \ ## Related Resources -- **Next:** [Discover Resources](/api-reference/v2/endpoint/discover-connector-resources) - inspect what's available before activating -- **Next:** [Configure Connector](/api-reference/v2/endpoint/configure-connector) - activate resources for sync +- **Next:** [Discover Resources](/api-reference/v2/endpoint/discover-connector-resources): inspect what's available before activating +- **Next:** [Configure Connector](/api-reference/v2/endpoint/configure-connector): activate resources for sync - **Teardown:** [Delete Connector](/api-reference/v2/endpoint/delete-connector) - **Read more:** [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) diff --git a/api-reference/v2/endpoint/create-tenant.mdx b/api-reference/v2/endpoint/create-tenant.mdx index 557b9e04..8ad5ba05 100644 --- a/api-reference/v2/endpoint/create-tenant.mdx +++ b/api-reference/v2/endpoint/create-tenant.mdx @@ -86,8 +86,8 @@ curl -X POST 'https://api.hydradb.com/databases' \ | Name | Description | | --- | --- | -| | Account-scoped database identifier. Use a stable, case-sensitive ID up to 25 characters; prefer lowercase letters, numbers, and underscores for portability. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Defines database-level metadata fields, the declared attributes you can filter on. Each entry is a schema field (below). See [Declare the schema](/essentials/v2/attributes#2-declare-the-schema) for detailed schema parameters. Formerly `tenant_metadata_schema`; the `tenant_metadata_schema` alias is still accepted (deprecated). (default=`null`) | +| | Database name, unique within your organization. Up to 255 characters, using only lowercase letters, digits, `-` and `_`. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | +| | Defines database-level metadata fields, the declared attributes you can filter on. Each entry is a schema field (below). Up to 32 fields, and at most 6 embedding flags in total: `enable_dense_embedding` and `enable_sparse_embedding` each count as one. See [Declare the schema](/essentials/v2/attributes#2-declare-the-schema) for detailed schema parameters. Formerly `tenant_metadata_schema`; the `tenant_metadata_schema` alias is still accepted (deprecated). (default=`null`) | ### Schema field @@ -150,7 +150,7 @@ Creation is asynchronous: the call returns as soon as provisioning starts. Alway 1. Create the database with `POST /databases` 2. **Default collection:** No collection exists until your first write. The first time you ingest without an explicit `collection`, HydraDB creates the database's default collection, which then stores all context written without a `collection`. Create additional collections at any time to scope data to users, teams, or projects. -3. **Retry failed databases:** If a database appears in `data.failed_databases`, re-create that database with `POST /databases` after addressing the reported issue. Poll status again before ingestion. +3. **Retry failed databases:** If a database appears in `data.failed_databases` of [List Databases](/api-reference/v2/endpoint/list-tenants), delete it with `DELETE /databases`, wait until it no longer appears in `GET /databases`, then create it again with `POST /databases`. The name stays taken until the failed database is deleted, so re-creating it directly returns `409 DATABASE_ALREADY_EXISTS`. 4. Start [ingesting context](/api-reference/v2/endpoint/ingest-context) once databases are ready 5. Check status of [ingestion](/api-reference/v2/endpoint/source-status). Start querying the database once the recently ingested sources show `completed` @@ -159,12 +159,12 @@ Creation is asynchronous: the call returns as soon as provisioning starts. Alway ## Defining metadata schema - Schema field names are **immutable** after database creation. You can add per-document free-form metadata fields at ingestion time, and add new database-level fields later with [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema), but updates are additive only: no delete, rename or type change, and data already ingested is not re-indexed for newly added dense/sparse metadata fields. Plan your schema carefully before creating the database. + Schema field names are **immutable** after database creation. You can add per-document free-form metadata fields at ingestion time, and add new database-level fields later with [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema), but updates are additive only: no delete, rename or type change, and a field added later cannot enable dense or sparse embeddings. Declare every field that needs semantic or BM25 search when you create the database. You can define a custom schema at database creation to enable exact-match metadata filtering (`enable_match`) or semantic/BM25 search over metadata text fields (`enable_dense_embedding` / `enable_sparse_embedding`). -For detailed parameters, valid data types, limits, shorthand flags, and comprehensive examples, see the [metadata](/essentials/v2/attributes) guide. +For parameters, data types, limits, shorthand flags, and examples, see the [Attributes](/essentials/v2/attributes) guide. --- @@ -176,5 +176,5 @@ For detailed parameters, valid data types, limits, shorthand flags, and comprehe - **Next:** [Ingest Context](/api-reference/v2/endpoint/ingest-context) - start ingesting data once status is ready - **Related:** [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema) - add metadata schema fields later - **Related:** [Delete Database](/api-reference/v2/endpoint/delete-tenant) - teardown -- **Read more:** [Concepts → Multi-Tenant Support](/essentials/v2/databases-and-collections) -- **Read more:** [Usage → Metadata](/essentials/v2/attributes) +- **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) +- **Read more:** [Attributes](/essentials/v2/attributes) diff --git a/api-reference/v2/endpoint/delete-collection.mdx b/api-reference/v2/endpoint/delete-collection.mdx index 02267458..4c3af685 100644 --- a/api-reference/v2/endpoint/delete-collection.mdx +++ b/api-reference/v2/endpoint/delete-collection.mdx @@ -59,7 +59,7 @@ curl -X DELETE 'https://api.hydradb.com/databases/collections?database=my_first_ "data": null, "error": { "code": "DATABASE_NOT_FOUND", - "message": "Database not found" + "message": "database my_first_database does not exist" }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -76,7 +76,7 @@ Deletion is asynchronous: a `200` means cleanup was accepted, not that it finish - Every ingest, query, and read addressed to it returns `404`, so nothing can write into a collection that is being purged. Sibling collections in the same database, and the database itself, are unaffected and keep serving normally. - Ingestion already in flight for this collection is cancelled before any store is purged, so a job that started before the delete cannot repopulate it afterwards. -- The collection disappears from [List Collections](/api-reference/v2/endpoint/list-sub-tenants) once its records are dropped, which happens before the slower vector, graph, and object-store cleanup completes. +- The collection disappears from [List Collections](/api-reference/v2/endpoint/list-sub-tenants) as soon as the delete is accepted, before the vector, graph, and object-store cleanup completes. There is no collection-level completion endpoint, and a repeated `DELETE` returning `200` is not a completion signal either. @@ -84,10 +84,6 @@ You do not need one to reuse the name safely. Ingestion creates a missing collec ## Behavior notes - -**Irreversible action.** Ingested context items, embeddings, graph nodes, and storage objects for this collection are permanently removed. Other collections in the same database are not touched. There is no recovery window. - - - **Async cleanup:** The endpoint returns immediately after accepting the request. Cleanup of vector stores, graphs, and storage objects runs in the background. - **Repeat calls are the retry path:** Deleting the same collection again is idempotent. A duplicate call while cleanup is still running joins the delete in progress rather than starting a second one. If a cleanup fails part-way, the collection stays fenced and re-issuing the same `DELETE` re-runs it. - **Stopping work first is still kinder:** The API cancels this collection's in-flight ingestion for you, but a job cancelled mid-run is reported as failed to whatever started it. Draining your own writers first avoids that noise. @@ -95,7 +91,7 @@ You do not need one to reuse the name safely. Ingestion creates a missing collec ## Errors -Common codes: `400 VALIDATION_ERROR`, `404 DATABASE_NOT_FOUND`, `401 UNAUTHORIZED`. See [Error Responses](/api-reference/v2/error-responses) for the full list. +Common codes: `400 INVALID_INPUT` (missing `database` or `collection`), `404 DATABASE_NOT_FOUND`, `401 UNAUTHORIZED`, `503 SERVICE_UNAVAILABLE` (a failed cleanup is still releasing the collection; retry shortly). See [Error Responses](/api-reference/v2/error-responses) for the full list.
@@ -105,5 +101,5 @@ Common codes: `400 VALIDATION_ERROR`, `404 DATABASE_NOT_FOUND`, `401 UNAUTHORIZE - **Before this:** [List Collections](/api-reference/v2/endpoint/list-sub-tenants) - find the collection ID - **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source): remove specific context items without deleting the collection - **Larger scope:** [Delete Database](/api-reference/v2/endpoint/delete-tenant) - remove the entire database -- **Read more:** [Concepts → Multi tenancy](/essentials/v2/databases-and-collections) +- **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) diff --git a/api-reference/v2/endpoint/delete-connector-resource.mdx b/api-reference/v2/endpoint/delete-connector-resource.mdx index e9cb767d..ea26bbf6 100644 --- a/api-reference/v2/endpoint/delete-connector-resource.mdx +++ b/api-reference/v2/endpoint/delete-connector-resource.mdx @@ -39,5 +39,5 @@ curl -X DELETE 'https://api.hydradb.com/connectors/{id}/resources/{resource_id}' ## Related Resources -- [List Connector Resources](/api-reference/v2/endpoint/connector-resources) - verify the resource is gone -- [Add Connector Resource](/api-reference/v2/endpoint/add-connector-resource) - add it back +- [List Connector Resources](/api-reference/v2/endpoint/connector-resources): verify the resource is gone +- [Add Connector Resource](/api-reference/v2/endpoint/add-connector-resource): add it back diff --git a/api-reference/v2/endpoint/delete-connector.mdx b/api-reference/v2/endpoint/delete-connector.mdx index 65c1724e..25ac7141 100644 --- a/api-reference/v2/endpoint/delete-connector.mdx +++ b/api-reference/v2/endpoint/delete-connector.mdx @@ -4,7 +4,7 @@ description: "Permanently remove a connector and stop all associated syncs." openapi: "api-reference/v2/openapi.json DELETE /connectors/{id}" --- -Deletes the connector and all its configured resources. Synced objects already ingested into your database are not removed. +Deletes the connector, all its configured resources, and its stored credentials. Synced objects already ingested into your database are not removed. @@ -37,5 +37,5 @@ curl -X DELETE 'https://api.hydradb.com/connectors/{connector_id}' \ ## Related Resources -- [List Connectors](/api-reference/v2/endpoint/list-connectors) - verify the connector no longer appears -- [Create Connector](/api-reference/v2/endpoint/create-connector) - start fresh +- [List Connectors](/api-reference/v2/endpoint/list-connectors): verify the connector no longer appears +- [Create Connector](/api-reference/v2/endpoint/create-connector): start fresh diff --git a/api-reference/v2/endpoint/delete-source.mdx b/api-reference/v2/endpoint/delete-source.mdx index 9b6989cc..c6c36450 100644 --- a/api-reference/v2/endpoint/delete-source.mdx +++ b/api-reference/v2/endpoint/delete-source.mdx @@ -116,7 +116,7 @@ curl -X DELETE 'https://api.hydradb.com/context' \ ## Status codes -**By default, every outcome returns `200`** - including a delete that removed +**By default, every outcome returns `200`**, including a delete that removed nothing. The real result is in the body, so check `data.deleted_count` and `data.results[]` rather than the status code. @@ -132,7 +132,7 @@ curl -X DELETE 'https://api.hydradb.com/context' \ Send `X-HydraDB-Delete-Status: strict` and a delete that did not happen returns `404`, `409`, or `500` instead of `200`. **This is the recommended mode for new -integrations** - it is the only way to detect a failed delete from the status +integrations**: it is the only way to detect a failed delete from the status code alone. ```bash Strict - honest status codes @@ -150,19 +150,19 @@ In strict mode: | --- | --- | --- | | `200` | n/a | At least one source was deleted. Check `results[]` for per-ID outcomes. | | `404` | `NOT_FOUND` | None of the given `ids` matched anything to delete. | -| `409` | `SOURCE_PROCESSING` | A source is still indexing. Retry after ingestion completes - see the `Retry-After` header. | +| `409` | `SOURCE_PROCESSING` | A source is still indexing. Retry after ingestion completes; see the `Retry-After` header. | | `500` | `INTERNAL_ERROR` | A store failed to remove the source. The delete is retryable. | Deleting a source that is still indexing is the case worth handling, and the main reason to turn strict mode on. Ingestion is asynchronous, so an - ingest-then-delete sequence - what most teardown and test scripts do - can + ingest-then-delete sequence, which most teardown and test scripts use, can reach the source before it finishes indexing. The source is **not** deleted. In the default mode that comes back as a `200` with `deleted_count: 0`, which is exactly the silent failure that leaves data behind. In strict mode it is a `409`. Retry once indexing completes, or poll - [Source Status](/api-reference/v2/endpoint/source-status) first. + [Ingestion Status](/api-reference/v2/endpoint/source-status) first. On `404`, `409`, and `500` the response `data` still carries the same @@ -211,7 +211,7 @@ The header always wins. Without it, the server default applies. When it happens, `X-HydraDB-Delete-Status: legacy` keeps the unconditional `200` for any integration that is not ready. Both header values are supported - and neither has a removal date - if that ever changes, we will announce it. + and neither has a removal date. If that ever changes, we will announce it. If your integration checks `response.ok` or `status == 200` today, it is treating blocked deletes as successful. That is the failure strict mode @@ -227,9 +227,9 @@ The header always wins. Without it, the server default applies. **Related Resources** - - **Find IDs:** [List Documents](/api-reference/v2/endpoint/list-documents) + - **Find IDs:** [List Context](/api-reference/v2/endpoint/list-documents) - **Perform a query:** [Query](/api-reference/v2/endpoint/query) - **Re-add content:** [Ingest Context](/api-reference/v2/endpoint/ingest-context) - - **Bigger hammer:** [Delete Database](/api-reference/v2/endpoint/delete-tenant) - removes the entire database + - **Larger scope:** [Delete Database](/api-reference/v2/endpoint/delete-tenant) - removes the entire database - **Read more:** [Context Management - Overview](/api-reference/v2/endpoint/sources-overview) diff --git a/api-reference/v2/endpoint/delete-tenant.mdx b/api-reference/v2/endpoint/delete-tenant.mdx index aadb8e57..d5b877b4 100644 --- a/api-reference/v2/endpoint/delete-tenant.mdx +++ b/api-reference/v2/endpoint/delete-tenant.mdx @@ -9,7 +9,7 @@ import { Field } from "/snippets/field.jsx"; This action is irreversible. Deleting a database removes all of its associated data, including all context items, embeddings, graph data, and the metadata schema. There is no soft-delete and no recovery window. -The examples below use a placeholder name, `database_to_delete`. Replace it with the database you actually mean to destroy before running them - and check the name twice on a shared or team account, where you may not be the only one using it. +The examples below use a placeholder name, `database_to_delete`. Replace it with the database you mean to destroy before running them, and check the name twice on a shared or team account. @@ -41,7 +41,7 @@ curl -X DELETE 'https://api.hydradb.com/databases?database=database_to_delete' \ "data": { "database": "database_to_delete", "status": "deletion_scheduled", - "message": "Database deregistered. Background cleanup is in progress." + "message": "Tenant deregistered. Background cleanup is in progress." }, "error": null, "meta": { @@ -57,7 +57,7 @@ curl -X DELETE 'https://api.hydradb.com/databases?database=database_to_delete' \ "data": null, "error": { "code": "DATABASE_NOT_FOUND", - "message": "Database not found" + "message": "database database_to_delete does not exist" }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -72,21 +72,17 @@ curl -X DELETE 'https://api.hydradb.com/databases?database=database_to_delete' \ Deletion is asynchronous. Treat deletion as complete when the database no longer appears in `GET /databases`, or when `GET /databases/status?database=...` returns `DATABASE_NOT_FOUND`. -After deletion completes, the same `database` can be used in a new `POST /databases` request. Until then, avoid recreating the database or retrying ingestion/query against it. +After deletion completes, the same `database` name can be used in a new `POST /databases` request. Until then, creating it again returns `409 DATABASE_ALREADY_EXISTS`. ## Behavior notes - -**Irreversible action.** Ingested context items, embeddings, graph nodes, metadata schema, and storage objects are permanently removed. There is no recovery window, so ensure you have a backup if the content matters. - - -- **Stop in-flight work first:** Stop all ingestion, polling, query, and background jobs targeting this database before deleting. Calls made after deregistration can fail with `DATABASE_NOT_FOUND` even while infrastructure cleanup is still running. +- **Stop in-flight work first:** Stop all ingestion, polling, query, and background jobs targeting this database before deleting. Ingest, query, and read calls made after deregistration return `404` even while infrastructure cleanup is still running. - **Async cleanup:** The endpoint returns immediately after deregistering the database. Infrastructure cleanup of vector stores, graphs, and storage objects runs in the background and may take a few minutes to complete. -- **Repeat calls:** Deleting an already-deleted database returns `404 DATABASE_NOT_FOUND`. Deleting a database that is still provisioning or deleting is treated as a request to tear down that database. +- **Repeat calls:** Deleting a database whose cleanup is still running returns `200` again. Once cleanup has finished, deleting it returns `404 DATABASE_NOT_FOUND`. Deleting a database that is still provisioning tears it down. ## Errors -Common codes: `404 DATABASE_NOT_FOUND`, `401 UNAUTHORIZED`, `422 VALIDATION_ERROR`. See [Error Responses](/api-reference/v2/error-responses) for the full list. +Common codes: `404 DATABASE_NOT_FOUND`, `401 UNAUTHORIZED`, `400 INVALID_INPUT` (missing `database`). See [Error Responses](/api-reference/v2/error-responses) for the full list.
@@ -96,5 +92,5 @@ Common codes: `404 DATABASE_NOT_FOUND`, `401 UNAUTHORIZED`, `422 VALIDATION_ERRO - **Before this:** [List Databases](/api-reference/v2/endpoint/list-tenants) - find the database ID - **Alternative:** [Delete Collection](/api-reference/v2/endpoint/delete-collection) - remove one collection without deleting the whole database - **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source): remove specific context items without deleting the whole database -- **Read more:** [Concepts → Multi-Tenant Support](/essentials/v2/databases-and-collections) +- **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) diff --git a/api-reference/v2/endpoint/discover-connector-resources.mdx b/api-reference/v2/endpoint/discover-connector-resources.mdx index 18e5aa0a..66f65668 100644 --- a/api-reference/v2/endpoint/discover-connector-resources.mdx +++ b/api-reference/v2/endpoint/discover-connector-resources.mdx @@ -6,7 +6,7 @@ openapi: "api-reference/v2/openapi.json GET /connectors/{id}/discover" import { Field } from "/snippets/field.jsx"; -Queries the provider (Slack, GitHub, Linear, Notion, or Gmail) and returns every resource available to the connector's credentials: channels, repos, teams, projects, databases, pages, or labels. Call this before [Configure](/api-reference/v2/endpoint/configure-connector) to decide which resources to activate. +Queries the provider and returns every resource available to the connector's credentials, such as channels, repos, teams, projects, databases, pages, or labels. Call this before [Configure](/api-reference/v2/endpoint/configure-connector) to decide which resources to activate. @@ -29,7 +29,6 @@ curl 'https://api.hydradb.com/connectors/{connector_id}/discover' \ ```json 200 { "provider": "slack", - "connector_id": "{connector_id}", "resources": [ { "id": "{resource_id}", @@ -47,11 +46,13 @@ curl 'https://api.hydradb.com/connectors/{connector_id}/discover' \ -Each item in `resources` represents one syncable unit. Pass the `id` and `resource_type` values to [Configure](/api-reference/v2/endpoint/configure-connector) to activate the ones you want. +Each item in `resources` represents one syncable unit. Pass the `id` (as `resource_id`) and `resource_type` values to [Configure](/api-reference/v2/endpoint/configure-connector) to activate the ones you want. + +To page through a large workspace, pass `limit` and, on later calls, the `cursor` from the previous response. A paginated response adds `next_cursor` and `has_more`; without either parameter the full list is returned.
## Related Resources -- **Next:** [Configure Connector](/api-reference/v2/endpoint/configure-connector) - activate discovered resources -- [Connector Resources](/api-reference/v2/endpoint/connector-resources) - see already-activated resources and their sync state +- **Next:** [Configure Connector](/api-reference/v2/endpoint/configure-connector): activate discovered resources +- [List Connector Resources](/api-reference/v2/endpoint/connector-resources): see already-activated resources and their sync state diff --git a/api-reference/v2/endpoint/fetch-content.mdx b/api-reference/v2/endpoint/fetch-content.mdx index b7f2ed06..42b604e4 100644 --- a/api-reference/v2/endpoint/fetch-content.mdx +++ b/api-reference/v2/endpoint/fetch-content.mdx @@ -61,10 +61,6 @@ curl -G 'https://api.hydradb.com/context/inspect' \ `inferred_content` is the enrichment the server wrote for the item, or `null` when there is none (for example an item ingested with `enrich: false`, or one whose enrichment has not finished). It is returned in `content` and `both` modes; `url` mode leaves it `null`. - -Use `mode=url` when a client should download the stored item directly. Use `mode=content` when you only need the text for display, summarization, or prompting. - - ### Mode examples These examples inspect an item ingested as text. @@ -83,7 +79,8 @@ These examples inspect an item ingested as text. "presigned_url": null, "content_type": "text/plain; charset=utf-8", "size_bytes": 73, - "message": "File fetched successfully" + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -106,7 +103,8 @@ These examples inspect an item ingested as text. "presigned_url": "https://storage.hydradb.com/.../policy_main?X-Amz-...", "content_type": "text/plain; charset=utf-8", "size_bytes": 73, - "message": "File fetched successfully" + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -129,7 +127,8 @@ These examples inspect an item ingested as text. "presigned_url": "https://storage.hydradb.com/.../policy_main?X-Amz-...", "content_type": "text/plain; charset=utf-8", "size_bytes": 73, - "message": "File fetched successfully" + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -155,7 +154,8 @@ These examples inspect an item ingested as text. "presigned_url": "https://storage.hydradb.com/.../policy_main?X-Amz-...", "content_type": "text/plain; charset=utf-8", "size_bytes": 73, - "message": "File fetched successfully" + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -171,7 +171,7 @@ These examples inspect an item ingested as text. "data": null, "error": { "code": "NOT_FOUND", - "message": "Source not found" + "message": "Source 'policy_main' not found. Verify the id is correct and the source has been ingested. See https://docs.hydradb.com/api-reference/v2/endpoint/fetch-content for usage details." }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -189,7 +189,6 @@ These examples inspect an item ingested as text. - **Items ingested as text:** There is no separate original file. `content` is the text you sent (a `conversation` item is stored as JSON, so its `content` is a JSON document), `content_type` reports how it was stored, and in `url` and `both` modes `presigned_url` downloads that same stored item. -- **`inferred_content`:** The enrichment the server wrote for the item, or `null` when there is none. Only `content` and `both` modes return it. - **Recently ingested items:** Fetching immediately after ingestion may return a record before enrichment is ready. For reliable reads, use [Ingestion Status](/api-reference/v2/endpoint/source-status) first. - **Presigned URL TTL:** The URL is valid only for `expiry_seconds`. Anyone with the URL can download the item during that window, so treat it as a short-lived secret. diff --git a/api-reference/v2/endpoint/get-connector-provider.mdx b/api-reference/v2/endpoint/get-connector-provider.mdx index dcf4ae02..be2a0582 100644 --- a/api-reference/v2/endpoint/get-connector-provider.mdx +++ b/api-reference/v2/endpoint/get-connector-provider.mdx @@ -55,6 +55,8 @@ The response identifies the provider's indexed streams, searchable values, exact | `searchable_fields` | Values rendered into the indexed document text. They are searchable, but cannot be targeted individually. | | `filterable_fields` | Exact-match filter definitions. Use each entry's `filter_key` in a query's `metadata_filters`. | | `credential_schema` | JSON Schema for the credentials required to connect. Omitted when unavailable. | +| `setup_guide` | Connect-time steps the credential schema cannot express, in reading order. Present only for providers that need them. | +| `token_scopes` / `token_scopes_note` | Permissions the provider token should carry (`id`, `required`, `reason`), or a note when the provider has no scope strings. Omitted when unknown. | Each `searchable_fields` and `filterable_fields` entry includes `name`, `data_type`, and an optional `description`; filterable entries also include `filter_key`. diff --git a/api-reference/v2/endpoint/get-connector.mdx b/api-reference/v2/endpoint/get-connector.mdx index a7f710d8..585517f5 100644 --- a/api-reference/v2/endpoint/get-connector.mdx +++ b/api-reference/v2/endpoint/get-connector.mdx @@ -25,23 +25,28 @@ curl 'https://api.hydradb.com/connectors/{connector_id}' \ ```json 200 { "connector_id": "{connector_id}", - "provider": "slack", - "name": "acme-engineering", "tenant_id": "acme_corp", "sub_tenant_id": "engineering", + "database": "acme_corp", + "collection": "engineering", + "name": "acme-engineering", + "provider": "slack", "provider_account_scope": "T12345ACME", "status": "active", "sync_status": "idle", "next_sync_at": "2026-06-01T13:00:00Z", - "sync_interval_seconds": 3600 + "sync_interval_seconds": 3600, + "lifecycle": "active" } ``` +Read `lifecycle` for what the connector is doing: `pending_setup`, `ingesting`, `syncing`, `active`, `paused` or `reconnect`. `status` is always `active` and kept for compatibility; `sync_status` is `syncing` only while a sync is running. +
## Related Resources - [List Connectors](/api-reference/v2/endpoint/list-connectors) -- [Connector Resources](/api-reference/v2/endpoint/connector-resources) - see per-resource sync state +- [List Connector Resources](/api-reference/v2/endpoint/connector-resources): see per-resource sync state diff --git a/api-reference/v2/endpoint/list-connector-providers.mdx b/api-reference/v2/endpoint/list-connector-providers.mdx index f0e4fa0d..67e7675d 100644 --- a/api-reference/v2/endpoint/list-connector-providers.mdx +++ b/api-reference/v2/endpoint/list-connector-providers.mdx @@ -26,9 +26,11 @@ curl 'https://api.hydradb.com/connectors/providers' \ "category": "Communication", "supported": true, "moveit_support": false, + "webhook_support": false, "is_alpha": false, "is_beta": false, - "rank": 1 + "rank": 1, + "rbac_support": false } ] } @@ -40,12 +42,13 @@ curl 'https://api.hydradb.com/connectors/providers' \ | --- | --- | | `provider` | Provider identifier. Use this value as `id` to get provider details and as `provider` when creating a connector. | | `category` | Display grouping for the provider. | -| `supported` | Whether the provider can be connected today. | -| `moveit_support` | Whether the provider syncs through the MOVEIT pipeline. | +| `supported` | Whether the provider can be connected. Only supported providers are listed, so this is always `true`. | +| `moveit_support` / `webhook_support` | Which sync engine serves the provider. Informational; you connect every provider the same way. | | `is_alpha` / `is_beta` | Connector maturity flags. | -| `rank` | Catalog display order; lower ranks appear first. | +| `rank` | Catalog display order; lower ranks appear first. `null` when unranked. | +| `rbac_support` | Reserved. Always `false` on this endpoint. | ## Related Resources -- [Get Connector Provider](/api-reference/v2/endpoint/get-connector-provider) - retrieve fields and credentials for one provider +- [Get Connector Provider](/api-reference/v2/endpoint/get-connector-provider): retrieve fields and credentials for one provider - [Create Connector](/api-reference/v2/endpoint/create-connector) diff --git a/api-reference/v2/endpoint/list-connectors.mdx b/api-reference/v2/endpoint/list-connectors.mdx index 27f6c32d..862dc7fa 100644 --- a/api-reference/v2/endpoint/list-connectors.mdx +++ b/api-reference/v2/endpoint/list-connectors.mdx @@ -4,7 +4,7 @@ description: "List all connectors for the authenticated organization." openapi: "api-reference/v2/openapi.json GET /connectors" --- -Returns all connectors belonging to the organization associated with the API key. +Returns all connectors belonging to the organization associated with the API key. Pass `provider` to list one provider's connectors, or `include=health` to add a `health` map from `connector_id` to `healthy`, `degraded`, `failed`, `checking` or `capped`. @@ -23,11 +23,15 @@ curl 'https://api.hydradb.com/connectors' \ "connectors": [ { "connector_id": "{connector_id}", - "provider": "slack", "tenant_id": "acme_corp", "sub_tenant_id": "engineering", + "database": "acme_corp", + "collection": "engineering", + "name": "acme-engineering", + "provider": "slack", "provider_account_scope": "T12345ACME", "status": "active", + "lifecycle": "active", "next_sync_at": "2026-06-01T13:00:00Z", "last_successful_sync_at": "2026-06-01T12:05:00Z", "last_attempted_sync_at": "2026-06-01T12:05:00Z" @@ -42,6 +46,6 @@ curl 'https://api.hydradb.com/connectors' \ ## Related Resources -- [Get Connector](/api-reference/v2/endpoint/get-connector) - fetch a single connector by ID +- [Get Connector](/api-reference/v2/endpoint/get-connector): fetch a single connector by ID - [Create Connector](/api-reference/v2/endpoint/create-connector) - [Delete Connector](/api-reference/v2/endpoint/delete-connector) diff --git a/api-reference/v2/endpoint/list-documents.mdx b/api-reference/v2/endpoint/list-documents.mdx index 3a108cab..77a4c7d9 100644 --- a/api-reference/v2/endpoint/list-documents.mdx +++ b/api-reference/v2/endpoint/list-documents.mdx @@ -9,7 +9,7 @@ import { Field } from "/snippets/field.jsx"; List the context items in a database or collection: everything you ingested and everything your connectors synced, in one paginated listing. Each row carries an item's `id` and its metadata; fetch the full content of one item with [Inspect Context](/api-reference/v2/endpoint/fetch-content). -Supports pagination, metadata filters, and field projection. For metadata design and query-time behavior, see [Scoping using metadata](/essentials/v2/attributes). +Supports pagination, metadata filters, and field projection. For metadata design and query-time behavior, see [Attributes](/essentials/v2/attributes). @@ -74,9 +74,9 @@ curl -X POST 'https://api.hydradb.com/context/list' \ ### 1. Filters -- `filters` is a structured object with three optional categories. Filters are exact-match constraints i.e. filtered values are matched against stored values as exact values. The one exception is `source_fields.title`, which matches as a case-insensitive prefix. There are no range, contains, or OR operators on this endpoint; run multiple calls and merge client-side for OR behavior. +- `filters` is a structured object with three optional categories. Each filter is an exact match against the stored value. The one exception is `source_fields.title`, which matches as a case-insensitive prefix. There are no range, contains, or OR operators on this endpoint. A `null` filter value returns `400`. - **AND/OR:** All filter pairs combine with a logical AND. To express OR semantics, run multiple calls and union them client-side. -- `ids `**\+ filters:** When `ids` is non-empty, only those IDs are considered, but other `filters` still apply on top - useful for "show me items 1, 2, 3 that also belong to department=legal". +- **`ids` + filters:** When `ids` is non-empty, only those IDs are considered, and the other `filters` still apply on top. For example, list items 1, 2 and 3 only if they also have `department=legal`. ```json { @@ -90,9 +90,9 @@ curl -X POST 'https://api.hydradb.com/context/list' \ | Category | Matched against | Notes | | --- | --- | --- | -| | Context item's schema-aligned `metadata` payload | Use for database metadata fields. `tenant_metadata` is accepted as a legacy alias. Keys must be declared in the database's `database_metadata_schema` with `enable_match: true`; undeclared keys are silently ignored. | +| | Context item's schema-aligned `metadata` payload | Use for database metadata fields. `tenant_metadata` is accepted as a legacy alias. Each key is matched against the item's stored value; no `enable_match` declaration is needed on this endpoint. | | | Context item's `additional_metadata` payload | Free-form per-item JSON. No schema declaration required. `document_metadata` is accepted as a legacy alias. | -| | Built-in item fields: `type`, `title`, `description`, `url`, `timestamp`, and the connector fields `app_provider`, `app_kind`, `app_external_id`, `app_parent_id` | Use for connector categories or quick title lookups. `app_external_id` and `app_parent_id` are only unique per provider, so pair them with `app_provider`. | +| | Built-in item fields: `type`, `title`, `description`, `url`, `timestamp`, and the connector fields `app_provider`, `app_kind`, `app_external_id`, `app_parent_id` | Use for connector categories or quick title lookups. Any other key returns `400`. `app_external_id` and `app_parent_id` are only unique per provider, so pair them with `app_provider`. | ### 2. Including Fields for convenient data objects @@ -186,7 +186,7 @@ Fields a row does not have, or that `include_fields` left out, are omitted. "data": null, "error": { "code": "DATABASE_NOT_FOUND", - "message": "Database not found" + "message": "Database 'acme_corp' not found. Use GET /databases to list active databases." }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", diff --git a/api-reference/v2/endpoint/list-sub-tenants.mdx b/api-reference/v2/endpoint/list-sub-tenants.mdx index ca8e9288..30253576 100644 --- a/api-reference/v2/endpoint/list-sub-tenants.mdx +++ b/api-reference/v2/endpoint/list-sub-tenants.mdx @@ -4,13 +4,13 @@ description: "List collection IDs inside a database." openapi: "api-reference/v2/openapi.json GET /databases/collections" --- -1. The default collection is not created until the first write - no collection exists until then. Once you ingest without an explicit `collection`, the default collection is created and stores all context written without a `collection`. Create additional collections at any time to scope data to users, teams, or projects. -2. **Implicit creation.** Collections are auto-created when ingestion writes data under a new `collection`. The returned list grows organically as your application writes data under new values. +1. **Default collection.** No collection exists until the first write. The first ingest without an explicit `collection` creates the default collection, which stores all context written without a `collection`. +2. **Implicit creation.** Ingesting under a new `collection` value creates that collection, so the list grows as your application writes under new values. ```python Python SDK -response = client.databases.collections(database="your database id") +response = client.databases.collections(database="my_first_database") ``` ```typescript TypeScript SDK @@ -33,8 +33,8 @@ curl -X GET 'https://api.hydradb.com/databases/collections?database=my_first_dat { "success": true, "data": { - "collections": ["user_alex", "user_johndoe", "workspace_42", "collection_default_abc123"], - "message": "Successfully retrieved collection IDs" + "collections": ["collection_default_abc123", "user_alex", "user_johndoe", "workspace_42"], + "message": "Successfully retrieved sub-tenant IDs" }, "error": null, "meta": { @@ -49,7 +49,7 @@ curl -X GET 'https://api.hydradb.com/databases/collections?database=my_first_dat "success": true, "data": { "collections": [], - "message": "Successfully retrieved collection IDs" + "message": "Successfully retrieved sub-tenant IDs" }, "error": null, "meta": { @@ -65,7 +65,7 @@ curl -X GET 'https://api.hydradb.com/databases/collections?database=my_first_dat "data": null, "error": { "code": "DATABASE_NOT_FOUND", - "message": "Database not found" + "message": "database my_first_database does not exist" }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -81,7 +81,7 @@ curl -X GET 'https://api.hydradb.com/databases/collections?database=my_first_dat **Related Resources** - - **Inspect content:** [List Documents](/api-reference/v2/endpoint/list-documents) - scoped to a `collection` + - **Inspect content:** [List Context](/api-reference/v2/endpoint/list-documents) - scoped to a `collection` - **Delete a collection:** [Delete Collection](/api-reference/v2/endpoint/delete-collection) - **Inspect usage:** [Database Stats](/api-reference/v2/endpoint/tenant-stats) diff --git a/api-reference/v2/endpoint/list-tenants.mdx b/api-reference/v2/endpoint/list-tenants.mdx index e185c036..c8c69940 100644 --- a/api-reference/v2/endpoint/list-tenants.mdx +++ b/api-reference/v2/endpoint/list-tenants.mdx @@ -53,7 +53,7 @@ curl -X GET 'https://api.hydradb.com/databases' \ "failed_databases": [ { "database": "staging_import", - "error": "Provisioning failed. Re-create the database to retry." + "error": "Failed after 3 attempts: " } ], "message": "Successfully retrieved tenant IDs" @@ -72,7 +72,7 @@ curl -X GET 'https://api.hydradb.com/databases' \ "data": null, "error": { "code": "UNAUTHORIZED", - "message": "Missing, expired, or invalid API key" + "message": "Missing Authorization header" }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -97,7 +97,7 @@ curl -X GET 'https://api.hydradb.com/databases' \ ## Retry notes - If provisioning failed for a database, `data.failed_databases` contains diagnostic entries as shown in the **Provisioning issue** tab. -- **Retry failed databases:** If a database appears in `data.failed_databases`, re-create that database with `POST /databases` after addressing the reported issue. Poll status again before ingestion. +- **Retry failed databases:** Delete the failed database with `DELETE /databases`, wait until it no longer appears in `GET /databases`, then create it again with `POST /databases`. Re-creating it without deleting it first returns `409 DATABASE_ALREADY_EXISTS`.
@@ -108,5 +108,5 @@ curl -X GET 'https://api.hydradb.com/databases' \ - **Inspect:** [Database Status](/api-reference/v2/endpoint/tenant-status) - **Inspect:** [Database Stats](/api-reference/v2/endpoint/tenant-stats) - **Delete:** [Delete Database](/api-reference/v2/endpoint/delete-tenant) - - **Read more:** [Concepts → Multi-Tenant Support](/essentials/v2/databases-and-collections) + - **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) \ No newline at end of file diff --git a/api-reference/v2/endpoint/list-webhook-deliveries.mdx b/api-reference/v2/endpoint/list-webhook-deliveries.mdx index 500ac22a..0bd97a7b 100644 --- a/api-reference/v2/endpoint/list-webhook-deliveries.mdx +++ b/api-reference/v2/endpoint/list-webhook-deliveries.mdx @@ -6,13 +6,13 @@ openapi: "api-reference/v2/openapi.json GET /webhooks/indexing/deliveries" Returns the delivery history for your workspace, most recent first. Use it to investigate events that never arrived, or that arrived more than once. -Filter by `status` to isolate failures, and page through results with `limit` and `cursor`. +Filter by `status` to isolate failures, and page through results with `limit` (1 to 100, default 20) and the `next_cursor` from the previous page as `cursor`. | State | Meaning | |---|---| | `pending` | Recorded and waiting to be sent. | | `sweeping` | Claimed for delivery or retry. | -| `delivered` | Your endpoint returned a `2xx`. | +| `delivered` | Your endpoint returned a status below `400`. | | `failed` | An attempt failed and will be retried. | | `permanently_failed` | Retries are exhausted. No further attempts. | diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index 5e70b0b1..1a81b6da 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -36,13 +36,13 @@ linkStyle default stroke:#64748b,stroke-width:2px; |---|---|---| | | `string[]` or weighted object | Where to look. A list uses equal normalized weights; an object like `{ "user_alex": 2, "company": 1 }` ranks one scope above another without excluding either. Max 100 collections. `collection` selects a single one. | | | `"hybrid"`, `"text"` | Choose the matching method. Use `"hybrid"` by default and `"text"` for exact terms or phrases. | -| | `"fast"`, `"thinking"`, `"auto"` | Choose latency vs quality, or let HydraDB decide. `"fast"` for low-latency paths, `"thinking"` for multi-query retrieval, reranking and declared relations, `"auto"` to score the query and route to one of the two (defaults to `"thinking"` when the signal is inconclusive; also overrides `graph_context` to match; **the default if `mode` is omitted**). | +| | `"fast"`, `"thinking"`, `"auto"` | Choose latency vs quality, or let HydraDB decide. `"fast"` for low-latency paths, `"thinking"` for multi-query retrieval, reranking and declared relations, `"auto"` to score the query and route to one of the two (defaults to `"thinking"` when the signal is inconclusive; **the default if `mode` is omitted**). | | | integer | Control prompt size. Start with `10`, reduce for tight context windows, increase only when you rerank or summarize downstream. | -| | `0.0` to `1.0` or `"auto"` | Tune hybrid query. Lower values favor BM25 keywords; higher values favor semantic similarity. | +| | `0.0` to `1.0` or `"auto"` | Tune hybrid query. Lower values favor BM25 keywords; higher values favor semantic similarity. Defaults to `0.8`; `"auto"` also resolves to `0.8`. | | | object | Narrow candidates with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on the fields declared in `database_metadata_schema`. | | | boolean | Include graph paths in `graph[]`. On by default; set `false` for chunk-only responses. | | | boolean | Pull items linked with `forceful_relations` at ingest into `forceful_relations[]`. On by default; followed only in `thinking` mode. | -| | boolean | Adds app-aware retrieval for connector content while still querying the full selected scope. | +| | boolean | Adds app-aware retrieval for connector content while still querying the full selected scope. On by default; set `false` to skip it. | For filter design, read [Attributes](/essentials/v2/attributes) before creating database schemas. For exact request fields, defaults, and response shape, use [Query](/api-reference/v2/endpoint/query). @@ -53,7 +53,7 @@ For filter design, read [Attributes](/essentials/v2/attributes) before creating | User intent | Recommended config | |---|---| | Fast RAG over shared context | `collection` (the shared one), `query_by="hybrid"`, `mode="fast"`, `max_results=5-10`, `graph_context=false` | -| Highest-quality RAG | `query_by="hybrid"`, `mode="thinking"`, `graph_context=true`, `alpha="auto"` | +| Highest-quality RAG | `query_by="hybrid"`, `mode="thinking"`, `graph_context=true` | | Personalized answer | `collections={ "": 2, "": 1 }`, `query_by="hybrid"`, `mode="thinking"` | | A person's preferences only | `collection=""`, `query_by="hybrid"` | | Exact keyword or phrase | `query_by="text"`, `operator="phrase"` | @@ -159,7 +159,7 @@ Use text query when literal wording matters: legal clauses, SKUs, error codes, I | Key | Contents | | --- | --- | | `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content`, optional `enrichment` (a string), `enrichment_kind` and `temporal`. No source details; call `POST /context/list` with the `context_id` in `ids` for those. | -| `graph[]` | Paths through the context graph, deduplicated across both lanes and not capped: `origin` (`query_path` or `chunk_relation`), `triplets[]` and a `path_summary`, which is never empty. Each hop's `relation.chunk_id` names the chunk it came from, and `relation.timestamp` (Unix epoch seconds) is present when the edge has one; a `chunk_relation` path is only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk. | +| `graph[]` | Paths through the context graph, deduplicated across both origins and not capped: `origin` (`query_path` or `chunk_relation`), `triplets[]` and a `path_summary`, which is never empty. Each hop's `relation.chunk_id` names the chunk it came from, and `relation.timestamp` (Unix epoch seconds) is present when the edge has one; a `chunk_relation` path is only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk. | | `forceful_relations[]` | Chunks linked with `forceful_relations` at ingest, each with the `via` that brought it in. Followed only in `thinking` mode. | | `llm_prompt` | A server-built markdown string, ready to inject into a model call: results cited `[1]`, forceful relations `[R1]`, related facts labelled `[P1]` in `graph[]` order with each path's relevance when it has one, then temporal facts and sources. | diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index dee4bc3d..d1d29ea8 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -36,7 +36,6 @@ result = client.query( # Ranking and response controls. max_results=10, - alpha="auto", recency_bias=0.2, graph_context=True, @@ -64,7 +63,6 @@ const result = await client.query({ // Ranking and response controls. maxResults: 10, - alpha: "auto", recencyBias: 0.2, graphContext: true, @@ -75,7 +73,7 @@ const result = await client.query({ attributes: { department: { $eq: "support" } }, }); -console.log(result.data.llmPrompt); +console.log(result.data?.llmPrompt); ``` ```bash cURL @@ -90,7 +88,6 @@ curl -X POST 'https://api.hydradb.com/query' \ "query_by": "hybrid", "mode": "thinking", "max_results": 10, - "alpha": "auto", "recency_bias": 0.2, "graph_context": true, "follow_forceful_relations": true, @@ -336,7 +333,7 @@ result = client.query( ``` - HydraDB scores the query before retrieval and routes it to `"fast"` or `"thinking"`; a query naming several distinct entities like this one is likely to route to `"thinking"`. Use `"auto"` for traffic where query complexity varies call-to-call and you do not want to hand-pick per request. This is also the default: an omitted `mode` field behaves exactly like `mode: "auto"`. Set `mode` to `"fast"` or `"thinking"` explicitly if you want a deterministic pipeline instead. + HydraDB scores the query before retrieval and routes it to `"fast"` or `"thinking"`; a query naming several distinct entities like this one is likely to route to `"thinking"`. Use `"auto"` for traffic where query complexity varies call-to-call and you do not want to hand-pick per request. Set `mode` to `"fast"` or `"thinking"` explicitly if you want a deterministic pipeline instead. @@ -350,26 +347,26 @@ result = client.query( | | Query terms or natural-language question. Cannot be empty. | | | Retrieval method. See [Query methods](#decision-matrix). (default=`"hybrid"`) | | | BM25 operator for `query_by: "text"`. Ignored for `hybrid`. (default=`"or"`) | -| | Retrieval pipeline. Applies to `hybrid` only; ignored for `text`. `"auto"` scores the query and resolves it to `"fast"` or `"thinking"`, defaulting to `"thinking"` when the signal is inconclusive; it also overrides whatever `graph_context` you sent to match that resolved mode. (default=`"auto"`) | -| | Maximum chunks to return. Default `10`; maximum `50`. Start with `10`, use `5` for tight prompts, and increase only when reranking downstream. | -| | Hybrid weight (`1.0` = pure semantic, `0.0` = pure BM25). Applies to `query_by: "hybrid"` only. (default=`0.8`) | -| | Boost newer content. Send `0` to disable recency entirely. | -| | Restrict retrieval to these `context_id`s. A scoped search that matches nothing returns nothing. | -| | Restrict retrieval to items with one of these exact titles (case-insensitive, ORed). Intersected with `ids` when both are sent. | -| | Adds an app-aware retrieval lane for connector content (exact IDs, actors, thread and parent traversal) while still querying the full selected scope. Set `false` to skip it. | +| | Retrieval pipeline. Applies to `hybrid` only; ignored for `text`. `"auto"` scores the query and resolves it to `"fast"` or `"thinking"`, defaulting to `"thinking"` when the signal is inconclusive. (default=`"auto"`) | +| | Maximum chunks to return. Default `10`; maximum `250`. Start with `10`, use `5` for tight prompts, and increase only when reranking downstream. | +| | Hybrid weight (`1.0` = pure semantic, `0.0` = pure BM25). Applies to `query_by: "hybrid"` only. `"auto"` resolves to the default. (default=`0.8`) | +| | Boost newer content. Send `0` to disable recency entirely. (default=`0.4`) | +| | Restrict retrieval to these `context_id`s, at most 200. A scoped search that matches nothing returns nothing. | +| | Restrict retrieval to items with one of these exact titles (case-insensitive, ORed), at most 500. Intersected with `ids` when both are sent. | +| | Adds an app-aware retrieval step for connector content (exact IDs, actors, thread and parent traversal) while still querying the full selected scope. Set `false` to skip it. (default=`true`) | | | Principals to answer as: only items they may retrieve are returned. Omit, or send `[]` or `["*"]`, for no access scoping. See [Access control](/essentials/v2/access-control). | | | Hard filter on declared attributes with operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$exists`. Applies to chunks, forceful relations and graph paths alike. See [Filters](#decision-matrix). | -| | When `true`, includes graph paths in `graph[]`. Set to `false` when you only need ranked chunks; `graph` is then `[]`. Relations you supplied via [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) appear identically to extracted ones. (default=`true`) **Under `mode: "auto"`, this value is overridden by the resolved mode.** | +| | When `true`, includes graph paths in `graph[]`. Set to `false` when you only need ranked chunks; `graph` is then `[]`. Relations you supplied via [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) appear identically to extracted ones. (default=`true`) | | | Pull the items each hit declared with `forceful_relations` at ingest into `forceful_relations[]`. Declared relations are followed only in `thinking` mode. Set to `false` for `forceful_relations: []`. `query_forceful_relations` is the deprecated alias. (default=`true`) | | | Resolve time-based questions (current, as of, ranges, upcoming) and return matched facts in `chunks[].temporal`. Never changes which chunks are returned. (default=`true`) | | | ISO 8601 time to treat as now for temporal reasoning. Set it when replaying past conversations. | | | Override the temporal intent HydraDB would infer from the query. | -| | The older filter language, ANDed with `attributes` when both are sent. Prefer `attributes`; use `metadata_filters` only to filter on connector fields under `additional_metadata`, which `attributes` does not reach (see [Connectors](/essentials/v2/connectors)). | +| | Deprecated. The older filter language, ANDed with `attributes` when both are sent. Prefer `attributes`; use `metadata_filters` only to filter on connector fields under `additional_metadata`, which `attributes` does not reach (see [Connectors](/essentials/v2/connectors)). | **Tuning heuristics.**
    -
  • alpha: start at 0.8. Lower toward 0.3 to 0.5 when the query contains literal tokens (error codes, SKUs, product names). Raise toward 0.9 for conceptual questions. Use "auto" when query shape varies.
  • +
  • alpha: start at 0.8. Lower toward 0.3 to 0.5 when the query contains literal tokens (error codes, SKUs, product names). Raise toward 0.9 for conceptual questions.
  • recency_bias: send 0 for static reference material. Set 0.2 to 0.4 for mixed content, 0.6 to 0.8 for changelogs, news, or status updates.
  • max_results: start at 10. Drop to 5 for tight context windows; raise to 20 if you rerank downstream.
@@ -392,7 +389,7 @@ result = client.query( |---|---|---| | `"fast"` | Single query pass | Real-time chat, autocomplete, simple lookups. | | `"thinking"` | Multi-query expansion + reranking + declared relations | Complex queries, customer-facing answers, anything where quality matters. | - | `"auto"` *(default if `mode` is omitted)* | Scores the query before retrieval and routes to `"fast"` or `"thinking"`; defaults to `"thinking"` when the signal is inconclusive. Also overrides `graph_context` to match whichever mode it picks. | Mixed or unpredictable query traffic where you do not want to hand-pick per request. | + | `"auto"` *(default if `mode` is omitted)* | Scores the query before retrieval and routes to `"fast"` or `"thinking"`; defaults to `"thinking"` when the signal is inconclusive. | Mixed or unpredictable query traffic where you do not want to hand-pick per request. | `"auto"`'s resolved pipeline is not reported back in the response, so budget latency as thinking-level in the worst case. @@ -465,7 +462,7 @@ result = client.query( "relation": { "predicate": "managed by", "context": "Refund processing is managed by the Finance Department.", - "timestamp": 1782984600.0, + "timestamp": 1782984600, "relationship_id": "rel_managed_by", "chunk_id": "ck_policy_3" }, @@ -475,7 +472,7 @@ result = client.query( } } ], - "path_summary": "Refund processing is managed by the Finance Department." + "path_summary": "Refund Processing managed by Finance Department." }, { "origin": "chunk_relation", @@ -514,7 +511,7 @@ result = client.query( } } ], - "llm_prompt": "# Query results\n\n**Query:** How are refunds processed, and how should I answer this user?\n**Found:** 2 results across 2 sources · 2 related facts · 1 temporal fact · 1 forceful relation\nCite a result by its number in brackets, e.g. [1].\n\n## Results\n\n### 1. Refund policy\n- **Relevance:** 0.91 · **Collection:** support · **Type:** file\n- **Id:** refund-policy · **Last updated:** 2026-07-02\n\nRefunds are processed within 30 days of purchase by the Finance Department.\n\n**Enrichment:** Refund window is 30 days; Finance owns refund processing.\n\n---\n\n### 2. Support chat with Priya\n- **Relevance:** 0.84 · **Collection:** support · **Type:** message\n- **Id:** chat-2026-07-29 · **Last updated:** 2026-07-29\n\nuser: Keep refund answers short please\nassistant: Got it.\n\n**Enrichment:** User prefers short answers about refunds.\n\n## Forceful relations\n\nLinked to a result by the author at ingest time (forceful_relations), not by relevance to this query.\n\n### R1. Refund FAQ\n- **Linked from:** refund-policy · **Collection:** support\n- **Id:** refund-faq\n\nFAQ: refunds to a card take 5 to 7 business days to appear.\n\n## Related facts\n\n- [P1] **Refund Processing** -managed by→ **Finance Department** (relevance 0.81) [1]\n Refund processing is managed by the Finance Department.\n- [P2] **User** -prefers→ **short answers** (relevance 0.74) [2]\n The user prefers short answers about refunds.\n\n## Temporal facts\n\n- **Refund policy** *effective_from* → **June 2026** (from 2026-06-01, precision: month, status: ongoing; evidence: \"from June\") [1]\n\n## Sources\n\n1. **Refund policy** (file, id: refund-policy) · https://docs.acme.com/refunds · updated 2026-07-02\n2. **Support chat with Priya** (message, id: chat-2026-07-29) · updated 2026-07-29\n3. **Refund FAQ** (id: refund-faq)" + "llm_prompt": "# Query results\n\n**Query:** How are refunds processed, and how should I answer this user?\n**Found:** 2 results across 2 sources · 2 related facts · 1 temporal fact · 1 forceful relation\nCite a result by its number in brackets, e.g. [1].\n\n## Results\n\n### 1. Refund policy\n- **Relevance:** 0.91 · **Collection:** support · **Type:** file\n- **Id:** refund-policy · **Last updated:** 2026-07-02\n\nRefunds are processed within 30 days of purchase by the Finance Department.\n\n**Enrichment:** Refund window is 30 days; Finance owns refund processing.\n\n---\n\n### 2. Support chat with Priya\n- **Relevance:** 0.84 · **Collection:** support · **Type:** message\n- **Id:** chat-2026-07-29 · **Last updated:** 2026-07-29\n\nuser: Keep refund answers short please\nassistant: Got it.\n\n**Enrichment:** User prefers short answers about refunds.\n\n## Forceful relations\n\nLinked to a result by the author at ingest time (forceful_relations), not by relevance to this query.\n\n### R1. Refund FAQ\n- **Linked from:** refund-policy · **Collection:** support\n- **Id:** refund-faq\n\nFAQ: refunds to a card take 5 to 7 business days to appear.\n\n## Related facts\n\n- [P1] **Refund Processing** -managed by→ **Finance Department** [1]\n- [P2] **User** -prefers→ **short answers** (relevance 0.74) [2]\n The user prefers short answers about refunds.\n\n## Temporal facts\n\n- **Refund policy** *effective_from* → **June 2026** (from 2026-06-01, precision: month, status: ongoing; evidence: \"from June\") [1]\n\n## Sources\n\n1. **Refund policy** (file, id: refund-policy) · https://docs.acme.com/refunds · updated 2026-07-02\n2. **Support chat with Priya** (message, id: chat-2026-07-29) · updated 2026-07-29\n3. **Refund FAQ** (id: refund-faq)" }, "error": null, "meta": { @@ -550,7 +547,7 @@ result = client.query( | Key | Contents | | --- | --- | | `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content` (verbatim), `enrichment` (the extracted statement as a plain string, omitted when there is none), `enrichment_kind` (an optional label; omitted when none was set), `temporal[]` (only when the query engaged temporal reasoning; `{ content, start_date, end_date }`, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`). | -| `graph[]` | Paths through the context graph, query paths first then chunk expansions: `origin`, `triplets[]` of `source` / `relation` / `target`, plus `path_summary`. `origin` is `"query_path"` (grown from the entities in the query) or `"chunk_relation"` (the neighbourhood of a returned chunk, only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk). The array is deduplicated across both lanes and is not capped. `path_summary` is never empty: when the server wrote no summary, it narrates the hops. Entities are `{ entity_id, name }`; relations are `{ predicate, context, temporal_details?, timestamp?, relationship_id, chunk_id }`, where `temporal_details` is omitted when empty and `timestamp` (Unix epoch seconds, a float) is omitted when the edge has none. `[]` when `graph_context` is `false`. | +| `graph[]` | Paths through the context graph, query paths first then chunk expansions: `origin`, `triplets[]` of `source` / `relation` / `target`, plus `path_summary`. `origin` is `"query_path"` (grown from the entities in the query) or `"chunk_relation"` (the neighbourhood of a returned chunk, only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk). The array is deduplicated across both origins and is not capped. `path_summary` is never empty: when the server wrote no summary, it narrates the hops. Entities are `{ entity_id, name }`; relations are `{ predicate, context, temporal_details?, timestamp?, relationship_id, chunk_id }`, where `temporal_details` is omitted when empty and `timestamp` (Unix epoch seconds, a float) is omitted when the edge has none. `[]` when `graph_context` is `false`. | | `forceful_relations[]` | Chunks pulled in through `forceful_relations` declared at ingest, followed only in `thinking` mode: `via.from` (the context whose declaration pulled it in, may be `""`), `via.to` (the chunk's own `context_id`), `chunk` (same shape as `chunks[]`). `[]` when none, when `follow_forceful_relations` is `false`, or when the query ran in `fast` mode. | | `llm_prompt` | A server-built markdown string ready to inject into a model call: `# Query results`, then `## Results`, `## Forceful relations`, `## Related facts`, `## Temporal facts` (with a `**Duration:**` line for a "how long between" question), `## Source facts`, `## Profiles`, `## Code search` and `## Sources`, each left out when empty. Source facts, profiles, code-search answers and the duration are prompt only: no JSON key carries them. Results are cited `[1]` and forceful relations `[R1]`; related facts are labelled `[P1]`, `[P2]`, ... in `graph[]` order, as in `- [P1] **Refunds** -managed_by→ **Finance** (relevance 0.81) [1]`: the parenthetical is the path's relevance after reranking and is left out when the path has none, and the line ends with the results the path was extracted from. Sources print only web (`http` or `https`) links. `""` only when the query found nothing at all. The layout is on [Query](/essentials/v2/query#llm_prompt). | @@ -573,14 +570,13 @@ To show a chunk's graph paths under that chunk, group hops by `triplets[].relati **Important Considerations & Common Mistakes** -- **`mode: "auto"` overrides `graph_context`.** Whatever you send for `graph_context` is replaced to match the resolved mode: `true` if auto escalates to `thinking`, `false` if it resolves to `fast`. This also applies when `mode` is omitted. Set `graph_context` explicitly only when calling `"fast"` or `"thinking"` directly. -- **Filter with `attributes`, on declared fields.** A key that is not in `database_metadata_schema`, or a value sent in `custom_attributes`, never matches. +- **Filter with `attributes`, on declared fields.** On a database with a `database_metadata_schema`, a key that is not declared in it is a `400`; `custom_attributes` are not filterable with `attributes`. - **Common mistakes.** Check [Ingestion Status](/api-reference/v2/endpoint/source-status) for recently ingested items before querying. If you omit `collection` and `collections`, HydraDB queries the default collection; use [List Collections](/api-reference/v2/endpoint/list-sub-tenants) to discover available IDs. ## Errors -Common codes: `400 INVALID_INPUT` (empty `query`), `400 VALIDATION_ERROR` (a malformed `attributes` filter), `404 DATABASE_NOT_FOUND`, `500 INTERNAL_ERROR`. See [Error Responses](/api-reference/v2/error-responses) for the full list. +Common codes: `400 INVALID_INPUT` (empty `query`), `400 VALIDATION_ERROR` (a malformed `attributes` filter), `404 DATABASE_NOT_FOUND`, `422 TENANT_INFRA_NOT_READY` (the database is still provisioning), `500 INTERNAL_ERROR`. See [Error Responses](/api-reference/v2/error-responses) for the full list. `400` also covers oversized filters: an `attributes` list above 500 values, or an `attributes` object above 64 KiB of compact JSON. The message names the offending key or reports the actual byte count. See [Attributes](/essentials/v2/attributes). diff --git a/api-reference/v2/endpoint/retry-webhook-delivery.mdx b/api-reference/v2/endpoint/retry-webhook-delivery.mdx index 836491eb..77b63fbb 100644 --- a/api-reference/v2/endpoint/retry-webhook-delivery.mdx +++ b/api-reference/v2/endpoint/retry-webhook-delivery.mdx @@ -4,7 +4,7 @@ description: "Queue a failed webhook delivery to be attempted again." openapi: "api-reference/v2/openapi.json POST /webhooks/indexing/deliveries/{delivery_id}/retry" --- -Queues a failed delivery for another attempt. Use it after fixing the problem on your side, such as a receiver that was down or was rejecting valid signatures. +Queues a failed delivery for another attempt. Use it after fixing the problem on your side, such as a receiver that was down or was rejecting valid signatures. Only `failed` and `permanently_failed` deliveries can be retried; for any other state the call returns `200` with `queued: false` and a message naming the current state. The retry is signed with your **current** signing secret, not the one in force when the delivery was first attempted. If you have rotated since, your receiver must know the new secret. diff --git a/api-reference/v2/endpoint/source-relations.mdx b/api-reference/v2/endpoint/source-relations.mdx index cffd12ea..473b1e13 100644 --- a/api-reference/v2/endpoint/source-relations.mdx +++ b/api-reference/v2/endpoint/source-relations.mdx @@ -91,14 +91,16 @@ Each entity (`source`, `target`) carries `name`, `type`, `namespace`, `entity_id "type": "Service", "namespace": "default", "entity_id": "entity_payments_worker", - "identifier": null + "identifier": null, + "provider": "" }, "target": { "name": "OrdersDB", "type": "Database", "namespace": "default", "entity_id": "entity_orders_db", - "identifier": null + "identifier": null, + "provider": "" }, "relations": [ { @@ -122,7 +124,7 @@ Each entity (`source`, `target`) carries `name`, `type`, `namespace`, `entity_id "is_truncated": false, "next_cursor": null, "success": true, - "message": "Relations retrieved successfully" + "message": "Successfully fetched relations for source" }, "error": null, "meta": { @@ -142,30 +144,41 @@ Each entity (`source`, `target`) carries `name`, `type`, `namespace`, `entity_id "name": "PaymentsWorker", "type": "Service", "namespace": "default", - "entity_id": "entity_payments_worker" + "entity_id": "entity_payments_worker", + "identifier": null, + "provider": "" }, "target": { "name": "OrdersDB", "type": "Database", "namespace": "default", - "entity_id": "entity_orders_db" + "entity_id": "entity_orders_db", + "identifier": null, + "provider": "" }, "relations": [ { "canonical_predicate": "DEPENDS_ON", "raw_predicate": "depends on", "context": "PaymentsWorker depends on OrdersDB for transaction sync.", - "relationship_id": "rel_payments_orders", - "confidence": 0.88 + "confidence": 0.88, + "temporal_details": null, + "timestamp": "2026-05-12T08:14:00Z", + "relationship_id": "rel_payments_orders_2", + "chunk_id": "policy_main_chunk_4", + "source_entity_id": "entity_payments_worker", + "target_entity_id": "entity_orders_db" } ], "chunk_id": "policy_main_chunk_4" } ], + "auxiliary_relations": [], + "auxiliary_truncated": false, "is_truncated": true, "next_cursor": 0.88, "success": true, - "message": "Relations retrieved successfully" + "message": "Successfully fetched relations for source" }, "error": null, "meta": { @@ -180,8 +193,8 @@ Each entity (`source`, `target`) carries `name`, `type`, `namespace`, `entity_id "success": false, "data": null, "error": { - "code": "SOURCE_NOT_FOUND", - "message": "Source not found" + "code": "DATABASE_NOT_FOUND", + "message": "Database 'acme_corp' not found. Use GET /databases to list active databases." }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -214,7 +227,7 @@ while True: ## Some additional notes - **Cursor opacity.** `next_cursor` is opaque (currently a numeric score). Don't construct it client-side or assume meaning - pass back exactly what the server returned. + **Cursor opacity.** `next_cursor` is opaque (currently a numeric score). Don't construct it client-side or assume meaning; pass back exactly what the server returned. - **Collection-wide queries:** Omitting `id` returns relations across the entire collection. This is useful for full-graph exports; pair with a small `limit` and paginate. @@ -229,5 +242,5 @@ while True: - **Indexing status:** [Ingestion Status](/api-reference/v2/endpoint/source-status) - confirm the graph is complete - **Query with graph paths:** [Query](/api-reference/v2/endpoint/query) returns graph paths in `graph[]`, controlled by the `graph_context` request flag - - **Concepts:** [Concepts → Context Graphs](/essentials/v2/context-graphs) + - **Concepts:** [Context Graphs](/essentials/v2/context-graphs)
diff --git a/api-reference/v2/endpoint/source-status.mdx b/api-reference/v2/endpoint/source-status.mdx index 1cdc0d78..dbae4db0 100644 --- a/api-reference/v2/endpoint/source-status.mdx +++ b/api-reference/v2/endpoint/source-status.mdx @@ -8,7 +8,7 @@ import { Field } from "/snippets/field.jsx"; Since ingestion is asynchronous, use this endpoint to determine when context is ready to be retrieved. -Pass one or more IDs in `ids` to retrieve status. Works for every context item, whether you ingested it or a connector synced it. When passing multiple IDs on the query string, use either repeated params (`?ids=policy_main&ids=runbook_deploy`) or a single comma-joined value (`?ids=policy_main,runbook_deploy`); both forms are equivalent and can be mixed. Surrounding whitespace is trimmed and empty entries are dropped. For more information, see the [Ingest](/essentials/v2/ingest) guide. +Pass one or more IDs in `ids` to retrieve status. Works for every context item, whether you ingested it or a connector synced it. For more information, see the [Ingest](/essentials/v2/ingest) guide. **Prefer webhooks over polling?** Register a webhook for `indexing.status_changed` events and HydraDB will `POST` to your endpoint when content reaches a terminal state (`completed` or `errored`). See [Webhooks](/essentials/v2/webhooks) for setup and receiver examples. @@ -47,7 +47,7 @@ curl -G 'https://api.hydradb.com/context/status' \ | Name | Description | | --- | --- | -| | One or more `id` values returned at ingestion. Accepts the ID of any context item, including connector items. Pass either repeated params (`ids=a&ids=b`) or a single comma-joined value (`ids=a,b`). Source IDs never contain commas (they are rejected at ingest), so the comma-joined form always splits unambiguously. | +| | One or more `id` values returned at ingestion. Accepts the ID of any context item, including connector items. Pass either repeated params (`ids=a&ids=b`) or a single comma-joined value (`ids=a,b`); the two forms can be mixed. IDs never contain commas (they are rejected at ingest), so the comma-joined form always splits unambiguously. Surrounding whitespace is trimmed, and empty and duplicate entries are dropped. | | | Database the items belong to. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | | | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | @@ -62,6 +62,7 @@ curl -G 'https://api.hydradb.com/context/status' \ "id": "policy_main", "indexing_status": "completed", "error_code": "", + "error_message": "", "success": true, "message": "Processing status retrieved successfully" }, @@ -69,6 +70,7 @@ curl -G 'https://api.hydradb.com/context/status' \ "id": "runbook_deploy", "indexing_status": "graph_creation", "error_code": "", + "error_message": "", "success": true, "message": "Processing status retrieved successfully" }, @@ -76,9 +78,9 @@ curl -G 'https://api.hydradb.com/context/status' \ "id": "typo_in_id", "indexing_status": "errored", "error_code": "FILE_NOT_FOUND", - "error_message": "ID not found", + "error_message": "", "success": false, - "message": "Processing status retrieved successfully" + "message": "ID not found" } ] }, @@ -96,7 +98,7 @@ curl -G 'https://api.hydradb.com/context/status' \ "data": null, "error": { "code": "DATABASE_NOT_FOUND", - "message": "Database not found" + "message": "Database 'acme_corp' not found. Use GET /databases to list active databases." }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -116,22 +118,21 @@ Each entry in `data.statuses` describes one requested `id`: | `id` | string | The context item ID you asked about (echoed back). | | `indexing_status` | string | One of the [status values](#status-values) below. `errored` is terminal. | | `error_code` | string | Machine-readable reason an entry is `errored`; **empty string (`""`) when the entry is not errored.** See [`error_code` values](#error-code-values). | -| `error_message` | string | Human-readable explanation that accompanies a non-empty `error_code`; empty otherwise. | -| `success` | boolean | `false` when `indexing_status` is `errored`, otherwise `true`. Describes the item, **not** the HTTP request - a `200` response can contain `errored` items. | -| `message` | string | Status of the *lookup* itself ("Processing status retrieved successfully"). It does **not** describe the ingestion outcome - read `indexing_status` / `error_code` for that. | +| `error_message` | string | Human-readable explanation of an ingestion-pipeline `error_code`. Empty otherwise, including for `FILE_NOT_FOUND`. | +| `success` | boolean | `false` when `indexing_status` is `errored`, otherwise `true`. Describes the item, **not** the HTTP request: a `200` response can contain `errored` items. | +| `message` | string | Status of the *lookup* itself: "Processing status retrieved successfully", or "ID not found" for an unknown `id`. It does **not** describe the ingestion outcome; read `indexing_status` and `error_code` for that. | ### Error code values -`error_code` is the field that lets you tell a **caller mistake** apart from a **real ingestion failure** - a distinction you cannot make from `indexing_status: "errored"` alone. It is empty on any non-errored entry. +`error_code` tells a **caller mistake** apart from a **real ingestion failure**, which `indexing_status: "errored"` alone cannot. It is empty on any non-errored entry. | `error_code` | Meaning | What to do | | --- | --- | --- | -| `FILE_NOT_FOUND` | No source with this `id` exists in the given `database`/`collection` - usually a typo or an `id` that was never ingested (or whose status has expired). | Fix the `id`, or (re-)ingest the source. Not a processing failure - retrying the status call will not change it. | -| `INVALID_FILE_ID` | The `id` was empty or blank. | Send a non-empty `id`. | -| *ingestion-pipeline codes* | A genuine processing failure (e.g. `PARSE_FAILED`, `UNSUPPORTED_FORMAT`, `PROCESSING_FAILED`, `EMBEDDING_FAILED`, …). | Act on the specific code - see the [Error Responses reference](/api-reference/v2/error-responses#common-error-codes). Many are re-ingest-and-retry; some are terminal (unsupported format, empty content). | +| `FILE_NOT_FOUND` | No item with this `id` exists in the given `database` and `collection`: usually a typo, an `id` that was never ingested, or an item that was deleted. | Fix the `id`, or ingest the item. Not a processing failure: retrying the status call will not change it. | +| *ingestion-pipeline codes* | A genuine processing failure, reported as a numeric `E####` code (for example `E1001` parse failed, `E1002` unsupported format, `E4001` embedding failed). | Act on the specific code; see [Ingestion error codes](/api-reference/v2/error-responses#ingestion-error-codes). Many are re-ingest-and-retry; some are terminal (unsupported format, empty content). | - Branch on `error_code`, not on the text in `message` or `error_message`. `message` describes the lookup, not the ingestion result, and human-readable text may change. The full list of codes an `errored` entry can carry is in the [Error Responses reference](/api-reference/v2/error-responses#common-error-codes). + Branch on `error_code`, not on the text in `message` or `error_message`. `message` describes the lookup, not the ingestion result, and human-readable text may change. The codes an `errored` entry can carry are listed under [Ingestion error codes](/api-reference/v2/error-responses#ingestion-error-codes). ## Status values @@ -255,7 +256,7 @@ Typical processing time: **`graph_creation` is searchable.** Items in this state are already retrievable via `/query`. Wait for `completed` only when you specifically need full graph traversal (graph paths in `graph[]`, which the `graph_context` request flag turns on). -- **Unknown IDs return as `errored`:** If you pass an ID that does not exist (e.g., a typo), HydraDB returns an entry with `indexing_status: "errored"` and `error_code: "FILE_NOT_FOUND"` rather than silently dropping it. Use `error_code` to distinguish this from a genuine ingestion failure - see [`error_code` values](#error-code-values). +- **Unknown IDs return as `errored`:** If you pass an ID that does not exist (e.g., a typo), HydraDB returns an entry with `indexing_status: "errored"` and `error_code: "FILE_NOT_FOUND"` rather than silently dropping it. Use `error_code` to distinguish this from a genuine ingestion failure; see [`error_code` values](#error-code-values). ## Errors @@ -268,7 +269,7 @@ Common codes: `400 INVALID_INPUT`, `404 DATABASE_NOT_FOUND`, `422 VALIDATION_ERR - **Before this:** [Ingest Context](/api-reference/v2/endpoint/ingest-context) - to get the IDs - **After completion:** [Query](/api-reference/v2/endpoint/query) - - **After completion:** [Fetch Content](/api-reference/v2/endpoint/fetch-content) + - **After completion:** [Inspect Context](/api-reference/v2/endpoint/fetch-content) - **After completion:** [Context Relations](/api-reference/v2/endpoint/source-relations) - - **Read more:** [Usage → Ingest](/essentials/v2/ingest) + - **Read more:** [Ingest](/essentials/v2/ingest)
diff --git a/api-reference/v2/endpoint/sources-overview.mdx b/api-reference/v2/endpoint/sources-overview.mdx index 5f957168..42d98f49 100644 --- a/api-reference/v2/endpoint/sources-overview.mdx +++ b/api-reference/v2/endpoint/sources-overview.mdx @@ -52,13 +52,13 @@ flowchart LR - **IDs**: each item has a `context_id`, yours or generated. The ingest response reports it as `results[].id`. Use it for polling status, inspecting content, deleting, and inspecting relations. - **Attributes**: `attributes` are the declared, filterable fields from `database_metadata_schema`; `custom_attributes` are free-form and stored with the item. Filter queries with `attributes`. See [Attributes](/essentials/v2/attributes). - **Enrichment**: on by default (`enrich: true`). HydraDB extracts entities, relations and preferences from each item into the [context graph](/essentials/v2/context-graphs); the extracted text comes back on query as `enrichment`, separate from the item's own `content`. -- **Declared relations**: any item can name the items it relates to with `forceful_relations`, so they surface together at query time in `forceful_relations[]`. +- **Declared relations**: any item can name the items it relates to with `forceful_relations`, so they surface together in `forceful_relations[]` on a `thinking` query. ## Declared relations and attributes Declared relations pre-wire item relationships at ingestion time so that related items surface together during retrieval, before the graph layer discovers connections on its own. Think of them as explicit "see also" links between your items. -Paired with declared attributes, you get deterministic control over how results are filtered and ranked. +Declared attributes, sent on the same item, decide which items an `attributes` filter lets a query return. ```json { diff --git a/api-reference/v2/endpoint/subgraph.mdx b/api-reference/v2/endpoint/subgraph.mdx index c4abd2c9..fb8b201d 100644 --- a/api-reference/v2/endpoint/subgraph.mdx +++ b/api-reference/v2/endpoint/subgraph.mdx @@ -9,7 +9,7 @@ import { Field } from "/snippets/field.jsx"; This endpoint returns the **connected subgraph** of one ingested item: every item reachable from it through item-level relations, traversed breadth-first up to `depth` hops, together with the relations among those members and the structural graph around them (entities, comments, attachments, people). -It answers a different question from [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Relations are the entity-and-predicate triplets *extracted from text* (`PaymentsWorker → depends_on → OrdersDB`). The subgraph is about *items*: which Slack message replies to which, which page links to which, which ticket a comment belongs to. Use it after [Query](/api-reference/v2/endpoint/query) or [List Documents](/api-reference/v2/endpoint/list-documents) when a single result is not enough and you need what surrounds it. +It answers a different question from [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Relations are the entity-and-predicate triplets *extracted from text* (`PaymentsWorker → depends_on → OrdersDB`). The subgraph is about *items*: which Slack message replies to which, which page links to which, which ticket a comment belongs to. Use it after [Query](/api-reference/v2/endpoint/query) or [List Context](/api-reference/v2/endpoint/list-documents) when a single result is not enough and you need what surrounds it. @@ -41,7 +41,7 @@ hydradb --output json subgraph slack_C0BE77_1788320073 | jq '.sources[].source_i | Name | Description | | --- | --- | -| | The item to start from. Any `id` returned by Query, List Documents or Ingest. URL-encode it if it contains reserved characters. An id containing a literal `/` cannot be written as one path segment; pass those as `GET /context/subgraph?id=...` instead. | +| | The item to start from. Any `id` returned by Query, List Context or Ingest. URL-encode it if it contains reserved characters. An id containing a literal `/` cannot be written as one path segment; pass those as `GET /context/subgraph?id=...` instead. | ## Query parameters @@ -126,8 +126,8 @@ Fields a member does not have are omitted. ], "relations": [ { - "source": { "name": "C0BE77TPEU8:1788320073.073799", "type": "SOURCE", "namespace": "sources", "entity_id": "slack_C0BE77_1788320073", "identifier": null }, - "target": { "name": "C0BE77TPEU8:1788235712.185879", "type": "SOURCE", "namespace": "sources", "entity_id": "slack_C0BE77_1788235712", "identifier": null }, + "source": { "name": "C0BE77TPEU8:1788320073.073799", "type": "SOURCE", "namespace": "sources", "entity_id": "slack_C0BE77_1788320073", "identifier": null, "provider": "slack" }, + "target": { "name": "C0BE77TPEU8:1788235712.185879", "type": "SOURCE", "namespace": "sources", "entity_id": "slack_C0BE77_1788235712", "identifier": null, "provider": "slack" }, "relations": [ { "canonical_predicate": "same_thread", "raw_predicate": "same_thread", "context": "", "confidence": 1, "temporal_details": null, "timestamp": "2026-09-02T03:34:33Z", "relationship_id": "rel_same_thread_1", "chunk_id": null, "source_entity_id": null, "target_entity_id": null } ], @@ -136,8 +136,8 @@ Fields a member does not have are omitted. ], "auxiliary_relations": [ { - "source": { "name": "saivenu", "type": "ACTOR", "namespace": "actors", "entity_id": "actor_saivenu", "identifier": "saivenu" }, - "target": { "name": "C0BE77TPEU8:1788320073.073799", "type": "SOURCE", "namespace": "sources", "entity_id": "slack_C0BE77_1788320073", "identifier": null }, + "source": { "name": "saivenu", "type": "ACTOR", "namespace": "actors", "entity_id": "actor_saivenu", "identifier": "saivenu", "provider": "slack" }, + "target": { "name": "C0BE77TPEU8:1788320073.073799", "type": "SOURCE", "namespace": "sources", "entity_id": "slack_C0BE77_1788320073", "identifier": null, "provider": "slack" }, "relations": [ { "canonical_predicate": "sender", "raw_predicate": "SENDER", "context": "", "confidence": 1, "temporal_details": null, "timestamp": "2026-09-02T03:34:33Z", "relationship_id": "rel_sender_1", "chunk_id": null, "source_entity_id": null, "target_entity_id": null } ], @@ -148,7 +148,7 @@ Fields a member does not have are omitted. "is_truncated": false, "max_depth_reached": 1, "success": true, - "message": "Subgraph fetched successfully" + "message": "Successfully fetched source subgraph" }, "error": null, "meta": { @@ -170,7 +170,7 @@ Fields a member does not have are omitted. "is_truncated": false, "max_depth_reached": 0, "success": true, - "message": "Subgraph fetched successfully" + "message": "Successfully fetched source subgraph" }, "error": null, "meta": { @@ -199,7 +199,7 @@ Fields a member does not have are omitted. ## Reading the response -- **`sources[]`** are the members, the start item included at `depth: 0`. Every `source_id` is an id you can pass to [Fetch Content](/api-reference/v2/endpoint/fetch-content) for the full document, or back to this endpoint to re-centre the subgraph on it. `discovered_via` on each member is another member's `source_id`, so the list is also a tree. +- **`sources[]`** are the members, the start item included at `depth: 0`. Every `source_id` is an id you can pass to [Inspect Context](/api-reference/v2/endpoint/fetch-content) for the full content, or back to this endpoint to re-centre the subgraph on it. `discovered_via` on each member is another member's `source_id`, so the list is also a tree. - **`relations[]`** are the item-level relations *among the members* (declared `relates_to` links, plus `same_thread` and `child_of`), in the same triplet shape as [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Their endpoints are `SOURCE` entities whose `entity_id` is the item's id. - **`auxiliary_relations[]`** is the structural graph around the members: which person sent a message, which entities are mentioned in it, which comments and attachments hang off it. These are recorded from the item itself, not extracted from text, so their `context` is empty. - **Not included:** the chunk-level entity relations that [Query](/api-reference/v2/endpoint/query) returns as graph paths in `graph[]`. Those are a different read. @@ -221,7 +221,7 @@ Fields a member does not have are omitted. **Related Resources** - **Entity relations:** [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations) - the triplets extracted from text - - **Full content of a member:** [Fetch Content](/api-reference/v2/endpoint/fetch-content) + - **Full content of a member:** [Inspect Context](/api-reference/v2/endpoint/fetch-content) - **Query with graph paths:** [Query](/api-reference/v2/endpoint/query) returns graph paths in `graph[]`, controlled by the `graph_context` request flag - - **Concepts:** [Concepts → Context Graphs](/essentials/v2/context-graphs) + - **Concepts:** [Context Graphs](/essentials/v2/context-graphs) diff --git a/api-reference/v2/endpoint/submit-feedback.mdx b/api-reference/v2/endpoint/submit-feedback.mdx index 11c352ca..2f16f25c 100644 --- a/api-reference/v2/endpoint/submit-feedback.mdx +++ b/api-reference/v2/endpoint/submit-feedback.mdx @@ -6,18 +6,18 @@ openapi: "api-reference/v2/openapi.json POST /feedback" import { Field } from "/snippets/field.jsx"; -Report back on a query that already ran - what was missing, what was wrong, or that it was exactly right. Feedback feeds retrieval-quality work; it does **not** change the result of the query it refers to. +Report back on a query that already ran: what was missing, what was wrong, or that it was exactly right. Feedback feeds retrieval-quality work; it does **not** change the result of the query it refers to. Both people and agents can submit. An agent that can tell a retrieval was unhelpful is often the best source of signal you have, so `source` labels which one it was. ## Linking feedback to a query -Every HydraDB response carries a `request_id` in `meta`, and the same value in the `X-Request-ID` header. Send that id back and we can line your comment up with the exact query it is about - the text queried, what came back, how long it took. +Every HydraDB response carries a `request_id` in `meta`, and the same value in the `X-Request-ID` header. Send that id back and we can line your comment up with the exact query it is about: the text queried, what came back, how long it took. ```json Query response {6} { "success": true, - "data": { "chunks": [ /* ... */ ] }, + "data": { "chunks": [] }, "error": null, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -28,10 +28,10 @@ Every HydraDB response carries a `request_id` in `meta`, and the same value in t ``` -Send `request_id` back **exactly as you received it**. It must be the UUID from `meta.request_id` (or the `X-Request-ID` header) - any other value is rejected with `400`. +Send `request_id` back **exactly as you received it**. It must be the UUID from `meta.request_id` (or the `X-Request-ID` header); any other value is rejected with `400`. -Submit feedback for queries that **returned**. If the query itself failed, handle the error instead - there is no retrieval to judge, and the fix is in the request rather than in the index. +Submit feedback for queries that **returned**. If the query itself failed, handle the error instead: there is no retrieval to judge, and the fix is in the request rather than in the index. ## Fields @@ -42,7 +42,7 @@ The `request_id` from the target query's `meta`. Must be a UUID. What was right or wrong, in your own words. Up to 8000 characters. -Required **unless** you send `ground_truth` - every submission needs at least one of the two. +Required **unless** you send `ground_truth`: every submission needs at least one of the two. @@ -59,11 +59,11 @@ What you already know the right answer to be. See [Ground truth](#ground-truth). -`positive`, `negative`, or `neutral`. Optional - leaving it out is not the same as `neutral`; it records that you sent a comment without a rating. +`positive`, `negative`, or `neutral`. Optional. Leaving it out is not the same as `neutral`; it records that you sent a comment without a rating. -`user` _(default)_ or `agent` - who is submitting. +`user` _(default)_ or `agent`: who is submitting. @@ -71,7 +71,7 @@ Optional. Scopes the feedback to a database. Must be one your API key can reach. -Optional. Requires `database` - a collection is scoped to a database, so sending it alone returns `400`. +Optional. Requires `database`: a collection is scoped to a database, so sending it alone returns `400`. @@ -88,7 +88,7 @@ result = client.query( client.feedback.submit( request_id=result.meta.request_id, - feedback="Returned the 2023 policy - the current one is in the Q3 handbook.", + feedback="Returned the 2023 policy; the current one is in the Q3 handbook.", rating="negative", source="agent", database="acme_corp", @@ -104,7 +104,7 @@ const result = await client.query({ await client.feedback.submit({ requestId: result.meta.requestId, - feedback: "Returned the 2023 policy - the current one is in the Q3 handbook.", + feedback: "Returned the 2023 policy; the current one is in the Q3 handbook.", rating: "negative", source: "agent", database: "acme_corp", @@ -119,7 +119,7 @@ curl -X POST 'https://api.hydradb.com/feedback' \ -H "Content-Type: application/json" \ -d '{ "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", - "feedback": "Returned the 2023 policy - the current one is in the Q3 handbook.", + "feedback": "Returned the 2023 policy; the current one is in the Q3 handbook.", "rating": "negative", "source": "agent", "database": "acme_corp", @@ -184,22 +184,24 @@ curl -X POST 'https://api.hydradb.com/feedback' \ ## Ground truth -If you already know the right answer - you are running an evaluation set, or you know which document the user needed - send it. It is a much stronger signal than a comment, because we can score it without a human reading it. +If you already know the right answer (you are running an evaluation set, or you know which document the user needed), send it. It is a much stronger signal than a comment, because we can score it without a human reading it. ```json -"ground_truth": { - "answer": "Refunds are processed within 14 days.", - "source_ids": ["policy_2024", "handbook_q3"] +{ + "ground_truth": { + "answer": "Refunds are processed within 14 days.", + "source_ids": ["policy_2024", "handbook_q3"] + } } ``` -- **`answer`** - the response you expected. -- **`source_ids`** - the sources that actually contain the answer. This is the one that grades retrieval: it tells us whether the query surfaced those documents, and where they ranked. +- **`answer`**: the response you expected. +- **`source_ids`**: the sources that actually contain the answer. This is the one that grades retrieval: it tells us whether the query surfaced those documents, and where they ranked. -Send either on its own or both together. If `ground_truth` is your only signal, at least one of the two has to carry something - values that are empty or all whitespace are treated as not sent. +Send either on its own or both together. If `ground_truth` is your only signal, at least one of the two has to carry something: values that are empty or all whitespace are treated as not sent. -When you send `ground_truth`, the `feedback` comment becomes optional - an evaluation run with an answer key does not need prose for every row. A submission with neither is rejected. +When you send `ground_truth`, the `feedback` comment becomes optional: an evaluation run with an answer key does not need prose for every row. A submission with neither is rejected. ```python Evaluation run @@ -221,26 +223,24 @@ for case in eval_set: At eval volumes you may brush the rate limit, so keep the submission from ending the loop: an unguarded call means a single `429` loses every remaining case, not just the one it failed on. -Duplicate `source_ids` are collapsed and blank entries dropped, so you do not need to de-duplicate or filter your answer key first - a list that still has one real id in it is scored on that id. +Duplicate `source_ids` are collapsed and blank entries dropped, so you do not need to de-duplicate or filter your answer key first; a list that still has one real id in it is scored on that id. ## Submitting more than once -Each submission is stored separately - a second comment about the same query does not replace the first. Send several as your understanding of a bad result develops, and file feedback from more than one user on the same query. +Each submission is stored separately: a second comment about the same query does not replace the first. Send several as your understanding of a bad result develops, and file feedback from more than one user on the same query. ## Rate limit -100 submissions per minute per organization. Over that, you get `429` with a `Retry-After` header and a message naming the seconds to wait - it is safe to retry after waiting. - -The ceiling is well above normal use; an agent reporting on every query it makes will stay comfortably under it. +100 submissions per minute per organization. Over that, you get `429` with a `Retry-After` header and a message naming the seconds to wait; it is safe to retry after waiting. ## Errors | Status | When | | --- | --- | -| `400` | `request_id` missing or not a UUID; no usable signal - `feedback` blank or absent **and** `ground_truth` absent, empty, or blank; `feedback` too long; unknown `rating`/`source`; `collection` without `database` | +| `400` | `request_id` missing or not a UUID; no usable signal (`feedback` blank or absent **and** `ground_truth` absent, empty, or blank); `feedback`, `ground_truth` or `metadata` over its limits; unknown `rating`/`source`; `collection` without `database` | | `401` | Missing or invalid API key | | `404` | `database` does not exist or is not reachable by this key | -| `429` | Over the rate limit - see `Retry-After` | +| `429` | Over the rate limit; see `Retry-After` | | `500` | Feedback could not be stored. Nothing was recorded; retrying is safe | A `500` means the submission was **not** saved, so a retry cannot create a duplicate of something already stored. diff --git a/api-reference/v2/endpoint/sync-connector.mdx b/api-reference/v2/endpoint/sync-connector.mdx index 0c4bce20..24993266 100644 --- a/api-reference/v2/endpoint/sync-connector.mdx +++ b/api-reference/v2/endpoint/sync-connector.mdx @@ -4,7 +4,7 @@ description: "Trigger an on-demand sync for a connector." openapi: "api-reference/v2/openapi.json POST /connectors/{id}/sync" --- -Starts an immediate sync for all configured resources. Syncs also run on a schedule (default: hourly), so this endpoint is only needed when you want to force a sync outside the normal cadence. +Starts an immediate sync for all configured resources. Syncs also run on a schedule (hourly by default), so call this only to sync outside that cadence. It returns `409` when the connector is paused or has no active resources. @@ -33,11 +33,11 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/sync' \ -`202` means the sync was queued, not that it completed. Poll [Connector Resources](/api-reference/v2/endpoint/connector-resources) and check that `provider_cursor` advances to confirm the sync ran. +`202` means the sync was queued, not that it completed. Poll [List Connector Resources](/api-reference/v2/endpoint/connector-resources) and check that `provider_cursor` advances to confirm the sync ran.
## Related Resources -- [Connector Resources](/api-reference/v2/endpoint/connector-resources) - poll `provider_cursor` to confirm sync completion -- [Configure Connector](/api-reference/v2/endpoint/configure-connector) - activate resources before syncing +- [List Connector Resources](/api-reference/v2/endpoint/connector-resources): poll `provider_cursor` to confirm sync completion +- [Configure Connector](/api-reference/v2/endpoint/configure-connector): activate resources before syncing diff --git a/api-reference/v2/endpoint/tenant-stats.mdx b/api-reference/v2/endpoint/tenant-stats.mdx index 3759de2b..fcec1f3b 100644 --- a/api-reference/v2/endpoint/tenant-stats.mdx +++ b/api-reference/v2/endpoint/tenant-stats.mdx @@ -10,7 +10,7 @@ import { Field } from "/snippets/field.jsx"; Get the indexed row count for a database. Counts aggregate across all collections in the database. -The count is reported under two field names, `data.knowledge_collection` and `data.memory_collection`. Both are historical names for the database's one collection, so the two `row_count` values are always equal. Read either one. +The count is reported under two historical field names, `data.knowledge_collection` and `data.memory_collection`. The two `row_count` values are always equal; read either one. @@ -94,7 +94,7 @@ curl -X GET 'https://api.hydradb.com/databases/stats?database=my_first_database' "data": null, "error": { "code": "DATABASE_NOT_FOUND", - "message": "Database not found" + "message": "database my_first_database does not exist" }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -122,5 +122,5 @@ curl -X GET 'https://api.hydradb.com/databases/stats?database=my_first_database' - **List context items:** [List Context](/api-reference/v2/endpoint/list-documents) - **List collections:** [List Collections](/api-reference/v2/endpoint/list-sub-tenants) - **Check provisioning:** [Database Status](/api-reference/v2/endpoint/tenant-status) - - **Read more:** [Concepts → Multi-Tenant Support](/essentials/v2/databases-and-collections) + - **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) diff --git a/api-reference/v2/endpoint/tenant-status.mdx b/api-reference/v2/endpoint/tenant-status.mdx index 9bb73942..1c433cc6 100644 --- a/api-reference/v2/endpoint/tenant-status.mdx +++ b/api-reference/v2/endpoint/tenant-status.mdx @@ -7,8 +7,6 @@ description: "Check the readiness of a database's infrastructure." import { Field } from "/snippets/field.jsx"; -Database creation is asynchronous, check if your database is ready before executing ingestion or any queries. - Poll this endpoint until `data.infra.ready_for_ingestion` is `true`. That one flag is the readiness signal: the server derives it from the individual infrastructure flags below, so read it rather than combining them yourself. @@ -90,7 +88,7 @@ curl -X GET 'https://api.hydradb.com/databases/status?database=my_first_database "data": null, "error": { "code": "DATABASE_NOT_FOUND", - "message": "Database not found" + "message": "database my_first_database does not exist" }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -101,11 +99,7 @@ curl -X GET 'https://api.hydradb.com/databases/status?database=my_first_database -**Stale database IDs:** If `database` does not exist, the call returns `404 DATABASE_NOT_FOUND`. Always verify that the database was created successfully before polling. - - - **Common mistake:** `row_count` from [Database Stats](/api-reference/v2/endpoint/tenant-stats) counts individual chunks, not context items. For a distinct item count, use [List Context](/api-reference/v2/endpoint/list-documents) and read `pagination.total` from the response. - +**Stale database IDs:** If `database` does not exist, the call returns `404 DATABASE_NOT_FOUND`. A database that is being deleted returns `200` with every flag `false`.
diff --git a/api-reference/v2/endpoint/tenants-overview.mdx b/api-reference/v2/endpoint/tenants-overview.mdx index 18bef5e6..6aa5371a 100644 --- a/api-reference/v2/endpoint/tenants-overview.mdx +++ b/api-reference/v2/endpoint/tenants-overview.mdx @@ -9,13 +9,14 @@ Databases are physically isolated spaces for storing context. In most integratio | Endpoint | Method | SDK method | Purpose | Async? | | --- | --- | --- | --- | --- | -| [`/databases`](/api-reference/v2/endpoint/create-tenant) | `POST` | `databases.create` | Create a new isolated workspace | Yes | +| [`/databases`](/api-reference/v2/endpoint/create-tenant) | `POST` | `databases.create` | Create a new isolated database | Yes | | [`/databases`](/api-reference/v2/endpoint/delete-tenant) | `DELETE` | `databases.delete` | Permanently remove a database | Yes | | [`/databases`](/api-reference/v2/endpoint/list-tenants) | `GET` | `databases.list` | List all databases for the organization | No | | [`/databases/status`](/api-reference/v2/endpoint/tenant-status) | `GET` | `databases.status` | Check provisioning readiness | No | | [`/databases/stats`](/api-reference/v2/endpoint/tenant-stats) | `GET` | `databases.stats` | Monitor database load | No | -| [`/databases/collections`](/api-reference/v2/endpoint/list-sub-tenants) | `GET` | TypeScript: `databases.collections`
Python: `databases.collections` | List active collections | No | +| [`/databases/collections`](/api-reference/v2/endpoint/list-sub-tenants) | `GET` | `databases.collections` | List active collections | No | | [`/databases/collections`](/api-reference/v2/endpoint/delete-collection) | `DELETE` | TypeScript: `databases.deleteCollection`
Python: `databases.delete_collection` | Permanently remove one collection | Yes | +| [`/databases/{database}/metadata-schema`](/api-reference/v2/endpoint/update-metadata-schema) | `PATCH` | TypeScript: `databases.updateMetadataSchema`
Python: `databases.update_metadata_schema` | Add metadata schema fields | No | ## Typical call sequence @@ -39,6 +40,6 @@ GET /databases/stats -> check database health & growth ## Key concepts -- **Database** - A top-level isolated space. For example - you can dedicate one database to one enterprise customer. -- **Collection** - Partitions within a database for per-user separation. The first collection is created implicitly at ingestion. Collections are useful when you need to scope data per user, team, or customer within a single database. -- **Database Metadata & Schema** - Structured fields defined at database creation to enable query-time filtering. \ No newline at end of file +- **Database**: a top-level isolated space. For example, you can dedicate one database to each enterprise customer. +- **Collection**: a partition within a database, used to scope data per user, team, or customer. A collection is created implicitly the first time you ingest into it. +- **Database metadata schema**: declared fields you can filter on at query time. Define them at database creation and add more later with [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema). \ No newline at end of file diff --git a/api-reference/v2/endpoint/update-metadata-schema.mdx b/api-reference/v2/endpoint/update-metadata-schema.mdx index 94b6f897..4ff89dfc 100644 --- a/api-reference/v2/endpoint/update-metadata-schema.mdx +++ b/api-reference/v2/endpoint/update-metadata-schema.mdx @@ -9,7 +9,7 @@ import { Field } from "/snippets/field.jsx"; Use this endpoint to add new fields to a database's `database_metadata_schema`. - This endpoint is additive only. It cannot delete fields, rename fields, or change the type/flags of existing fields. + This endpoint is additive only. It cannot delete fields, rename fields, or change the type or flags of existing fields, and a new field cannot enable dense or sparse embeddings. @@ -27,10 +27,8 @@ curl -X PATCH 'https://api.hydradb.com/databases/acme_corp/metadata-schema' \ "enable_match": true }, { - "name": "summary_label", - "data_type": "VARCHAR", - "enable_dense_embedding": true, - "enable_sparse_embedding": true + "name": "priority", + "data_type": "INT64" } ] }' @@ -49,12 +47,7 @@ response = requests.patch( json={ "add_fields": [ {"name": "region", "data_type": "VARCHAR", "enable_match": True}, - { - "name": "summary_label", - "data_type": "VARCHAR", - "enable_dense_embedding": True, - "enable_sparse_embedding": True, - }, + {"name": "priority", "data_type": "INT64"}, ] }, ) @@ -71,12 +64,7 @@ const response = await fetch("https://api.hydradb.com/databases/acme_corp/metada body: JSON.stringify({ add_fields: [ { name: "region", data_type: "VARCHAR", enable_match: true }, - { - name: "summary_label", - data_type: "VARCHAR", - enable_dense_embedding: true, - enable_sparse_embedding: true, - }, + { name: "priority", data_type: "INT64" }, ], }), }); @@ -102,26 +90,26 @@ Each `add_fields[]` item uses the same field shape as `database_metadata_schema` | Field | Description | | --- | --- | -| | New metadata key. Must start with a letter or `_`, contain only letters/numbers/underscores, and not be a reserved system name. | +| | New metadata key. Must start with a letter, contain only letters, numbers and underscores, and not be a reserved system name. | | | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or friendly aliases such as `string`, `integer`, `float`, `boolean`, `object`. Defaults to `VARCHAR`. `ARRAY` is not supported and is rejected with `400`; for multi-value fields declare `VARCHAR` and store the values comma-joined. | | | Max length for `VARCHAR`. Default `1024`; maximum `65535`. | -| | Enables the intended exact-match metadata filtering path for this field. | -| | Adds dense semantic search for a `VARCHAR` metadata field. | -| | Adds sparse/BM25 search for a `VARCHAR` metadata field. | +| | Enables exact-match filtering on this field. (default=`false`) | +| | Not supported here: `true` on a new field returns `400`. Dense embeddings can only be declared at [database creation](/api-reference/v2/endpoint/create-tenant). | +| | Not supported here: `true` on a new field returns `400`. Sparse (BM25) embeddings can only be declared at database creation. | | | Backward-compatible shorthand for `enable_match: true`. Prefer `enable_match`. | ## Rules - Additions only. -- Existing field names cannot be reused, case-insensitively. +- Re-sending a field with exactly its existing definition is a no-op: the request succeeds and the field is left out of `added_fields`. Any other reuse of an existing name (compared case-insensitively) returns `409`. - Existing fields cannot be deleted or changed. - Total custom database metadata fields cannot exceed 32. - Reserved names such as `source_id`, `chunk_id`, `metadata`, and `document_metadata` are rejected. -- Dense/sparse embedding flags are only valid on `VARCHAR` fields. -- Filter indexes for `enable_match` fields are created before the merged schema is saved. +- `enable_dense_embedding` and `enable_sparse_embedding` are rejected on new fields. +- `ARRAY` fields are rejected. - This endpoint saves the updated schema and its filter indexes. It does not yet re-index data already ingested for newly added dense/sparse metadata fields. Create the desired semantic metadata fields before ingesting, or migrate/re-ingest into a database with the final schema if those fields must participate in semantic/BM25 metadata search. + A field that needs semantic or BM25 search must be declared when the database is created. To add one to an existing database, create a new database with the final schema and re-ingest into it. ## Response @@ -131,7 +119,8 @@ Each `add_fields[]` item uses the same field shape as `database_metadata_schema` ```json Success { "database": "acme_corp", - "added_fields": ["region", "summary_label"] + "tenant_id": "acme_corp", + "added_fields": ["region", "priority"] } ``` @@ -140,8 +129,8 @@ Each `add_fields[]` item uses the same field shape as `database_metadata_schema` "success": false, "data": null, "error": { - "code": "CONFLICT", - "message": "field \"region\" already exists in the schema" + "code": "INTERNAL_ERROR", + "message": "Schema conflict: field \"region\" already exists in the schema with a different definition. Resubmitting a field with its existing definition is accepted as a no-op, but an existing field cannot be modified. See https://docs.hydradb.com/api-reference/v2/endpoint/patch-metadata-schema for usage details. Re-send the field with its existing definition to make this a no-op, or use a different field name." }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -156,14 +145,14 @@ Each `add_fields[]` item uses the same field shape as `database_metadata_schema` | Status | When it happens | | --- | --- | -| `400` | Invalid request body, empty `add_fields`, invalid field name/type, too many fields, embedding enabled on a non-`VARCHAR` field. | +| `400` | Invalid request body, empty `add_fields`, invalid field name or type, `ARRAY` type, more than 32 fields in total, or `enable_dense_embedding` / `enable_sparse_embedding` on a new field. | | `404` | Database not found. | -| `409` | Field already exists or the update conflicts with stored database mapping/schema state. | +| `409` | A field with the same name exists with a different definition (or appears twice in `add_fields` with different definitions), or the database changed during the request. | | `500` | Backend persistence or index creation failed. | ## Related - [Create Database](/api-reference/v2/endpoint/create-tenant) -- [Scoping using metadata](/essentials/v2/attributes) +- [Attributes](/essentials/v2/attributes) - [Ingest Context](/api-reference/v2/endpoint/ingest-context) - [Query](/api-reference/v2/endpoint/query) diff --git a/api-reference/v2/endpoint/update-source-metadata.mdx b/api-reference/v2/endpoint/update-source-metadata.mdx index d74d46c5..3bc9bcb6 100644 --- a/api-reference/v2/endpoint/update-source-metadata.mdx +++ b/api-reference/v2/endpoint/update-source-metadata.mdx @@ -6,7 +6,7 @@ openapi: "api-reference/v2/openapi.json PATCH /context/{id}/metadata" import { Field } from "/snippets/field.jsx"; -Use this endpoint when you know a source ID and need to update its metadata in place. It updates both the source row and indexed chunk metadata used by query/list filters. +Use this endpoint when you know a source ID and need to update its metadata or access-control list in place. It updates both the source row and the indexed chunk metadata used by query and list filters. This endpoint uses older names for the fields you set at ingest: an item's `attributes` are `database_metadata` here, and its `custom_attributes` are `additional_metadata`. The source ID is the item's `context_id`. @@ -17,7 +17,7 @@ PATCH /context/{id}/metadata ``` - The legacy route `PATCH /context/sources/{source_id}/metadata` still works but is deprecated - migrate to the route above. Both behave identically; `source_id` and `id` name the same value. + The legacy route `PATCH /context/sources/{source_id}/metadata` still works but is deprecated; migrate to the route above. Both behave identically; `source_id` and `id` name the same value. @@ -105,13 +105,14 @@ const response = await fetch("https://api.hydradb.com/context/policy_main/metada | --- | --- | | | Owning database. (deprecated alias: `tenant_id`) | | | Collection that contains the source. This endpoint does not default it. (deprecated alias: `sub_tenant_id`) | -| | Schema-backed metadata fields to merge into the source's `metadata`. Keys must satisfy the tenant metadata schema when one exists. (deprecated alias: `tenant_metadata`) | +| | Schema-backed metadata fields to merge into the source's `metadata`. Keys must satisfy the database metadata schema when one exists. (deprecated alias: `tenant_metadata`) | | | Free-form metadata fields to merge into the source's `additional_metadata`. | +| | Replaces the source's access-control list; it does not merge. Send the complete new list, `[]` to make the source private, or `["__public__"]` to make it visible to every identified caller. Omit it to leave the list unchanged. See [Access control](/essentials/v2/access-control). | -At least one of `database_metadata` or `additional_metadata` is required. +At least one of `database_metadata`, `additional_metadata` or `acl` is required. - This edit endpoint uses `database_metadata` for schema-backed source metadata (deprecated alias: `tenant_metadata` - still accepted, but the canonical field wins if both are sent). The ingest names `attributes` and `custom_attributes`, and the `metadata` name on list rows, are not read by this PATCH body. `document_metadata` is rejected; use `additional_metadata`. + This edit endpoint uses `database_metadata` for schema-backed source metadata (deprecated alias: `tenant_metadata`, still accepted, but the canonical field wins if both are sent). The ingest names `attributes` and `custom_attributes`, and the `metadata` name on list rows, are not read by this PATCH body. `document_metadata` is rejected; use `additional_metadata`. ## Behavior @@ -122,7 +123,7 @@ At least one of `database_metadata` or `additional_metadata` is required. - The source must already exist. This endpoint does not create sources. - The endpoint edits one source at a time. Bulk metadata edits are not supported. - Updated metadata is visible to [`/query`](/api-reference/v2/endpoint/query) metadata filters and [`/context/list`](/api-reference/v2/endpoint/list-documents) filters. -- If an edited tenant metadata field has `enable_dense_embedding` or `enable_sparse_embedding`, HydraDB synchronously updates the search index for it. +- If an edited database metadata field has `enable_dense_embedding` or `enable_sparse_embedding`, HydraDB updates the search index for it before responding. If that index update fails, the edit is still saved and the response reports `vector_synced: false` with `vector_sync_error`; retry the same edit to converge it. - If the edited fields are `enable_match`-only, the search index needs no update and `vector_sync_required` is `false`. ## Response @@ -134,6 +135,8 @@ At least one of `database_metadata` or `additional_metadata` is required. "success": true, "data": { "id": "policy_main", + "database": "acme_corp", + "collection": "team_docs", "tenant_id": "acme_corp", "sub_tenant_id": "team_docs", "updated": true, @@ -158,6 +161,8 @@ At least one of `database_metadata` or `additional_metadata` is required. "success": true, "data": { "id": "policy_main", + "database": "acme_corp", + "collection": "team_docs", "tenant_id": "acme_corp", "sub_tenant_id": "team_docs", "updated": true, @@ -186,8 +191,8 @@ At least one of `database_metadata` or `additional_metadata` is required. "success": false, "data": null, "error": { - "code": "BAD_REQUEST", - "message": "invalid metadata edit: tenant_metadata.department must be of type VARCHAR" + "code": "INVALID_INPUT", + "message": "invalid metadata edit: metadata field \"department\" must be of type string, got number" }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -201,15 +206,19 @@ At least one of `database_metadata` or `additional_metadata` is required. | Field | Description | | --- | --- | | | Updated source ID. | -| | Public tenant ID. | -| | Sub-tenant that contained the source. | +| | Database named in the request. | +| | Collection that contained the source. | +| | Deprecated alias for `database`. | +| | Deprecated alias for `collection`. | | | `true` when the source metadata was updated. | | | Database metadata keys included in the request. | | | Deprecated alias for `database_metadata_keys`; still emitted for backward compatibility. | | | Additional metadata keys included in the request. | -| | `true` when at least one changed tenant metadata field has dense/sparse embedding enabled. | +| | `true` when the request replaced the source's `acl`. Omitted otherwise. | +| | `true` when at least one changed database metadata field has dense/sparse embedding enabled. | | | Present when sync was required. `true` means the sync completed. | | | Number of chunks synced to the vector store when sync was required. | +| | Present when a required sync failed. The metadata edit itself was saved; retry the same edit. | | | Deprecated alias for `vector_sync_required`; still emitted for backward compatibility. | | | Deprecated alias for `vector_synced`; still emitted for backward compatibility. | | | Deprecated alias for `vector_rows_synced`; still emitted for backward compatibility. | @@ -220,27 +229,27 @@ At least one of `database_metadata` or `additional_metadata` is required. | Status | When it happens | | --- | --- | -| `400` | Missing `database`, missing `collection`, empty metadata payload, `document_metadata` supplied, unknown tenant metadata key when a schema exists, wrong type, reserved key, over-size payload, too-deep nesting, or `null` for a dense/sparse-enabled field. | +| `400` | Missing `database`, missing `collection`, none of `database_metadata`, `additional_metadata` or `acl` supplied, `document_metadata` supplied, invalid `acl`, unknown database metadata key when a schema exists, wrong type, reserved key, over-size payload, too-deep nesting, or `null` for a dense/sparse-enabled field. | | `404` | Source does not exist for the `(database, collection, id)` scope. | -| `500` | Metadata was saved but the dense/sparse search index update failed. Retry the same edit; it is idempotent. | +| `500` | The edit could not be saved. Retry the same edit; it is idempotent. | ### Size limits `database_metadata` (and its still-accepted `tenant_metadata` alias) is capped at **16 KiB**; `additional_metadata` at **1 KiB**. Each cap applies to the whole map, -measured on its compact JSON encoding in UTF-8 bytes - keys, quotes and +measured on its compact JSON encoding in UTF-8 bytes: keys, quotes and punctuation count toward the budget, so budget in bytes rather than in characters of content. `document_metadata` has no size limit here because it is **not accepted on this - endpoint at all** - any non-null value returns `400`, whatever its size. It is a + endpoint at all**: any non-null value returns `400`, whatever its size. It is a valid alias for `additional_metadata` on [`/context/ingest`](/api-reference/v2/endpoint/ingest-context), but not on this one. Send `additional_metadata`. -The cap is checked against the payload in **this** request, before the merge - not +The cap is checked against the payload in **this** request, before the merge, not against the stored map the merge produces. A small edit to an already-large map is therefore accepted, so treat the cap as a per-request budget rather than a guarantee about the final stored size. Over-cap fails the whole edit with `400` and @@ -257,11 +266,11 @@ reports both numbers: } ``` -See [Scoping using metadata → Size limits](/essentials/v2/attributes#size-limits). +See [Attributes: Size limits](/essentials/v2/attributes#size-limits). ## Related -- [Scoping using metadata](/essentials/v2/attributes) +- [Attributes](/essentials/v2/attributes) - [Ingest Context](/api-reference/v2/endpoint/ingest-context) - [List Context](/api-reference/v2/endpoint/list-documents) - [Query](/api-reference/v2/endpoint/query) diff --git a/api-reference/v2/error-responses.mdx b/api-reference/v2/error-responses.mdx index 2f664fb6..13144729 100644 --- a/api-reference/v2/error-responses.mdx +++ b/api-reference/v2/error-responses.mdx @@ -22,16 +22,14 @@ HydraDB core endpoints (`/databases`, `/context/*`, and `/query`) use the same t } ``` - - -Field | Description | +| Field | Description | |---|---| | `success` | `false` for errors. | | `data` | Always `null` for error responses. | | `error.code` | Machine-readable code for programmatic handling. | | `error.message` | Human-readable explanation of what failed. | | `meta.request_id` | Request identifier. Include it when contacting support. | -| `meta.latency_ms` | Server-side processing time in milliseconds. +| `meta.latency_ms` | Server-side processing time in milliseconds. | Use `error.code` for branching and log `meta.request_id` for every failed request. The HTTP status tells you the class of failure; the error code tells you what to do. @@ -80,14 +78,14 @@ Endpoint pages list the most common codes for that operation. New codes may be a Asynchronous ingestion failures surface a numeric `E####` code in the `error_code` field of [`GET /context/status`](/api-reference/v2/endpoint/source-status) responses and `indexing.status_changed` [webhook](/essentials/v2/webhooks) payloads. Unlike the HTTP `error.code` values above (which describe why a *request* was rejected), these describe why a specific *item* failed to index. -Many storage- and capacity-related ingestion errors are **transient**: the pipeline retries them automatically with backoff, and they typically self-resolve within minutes. A code appearing in `error_code` does not by itself mean the item has failed permanently - only treat an item as a real failure once it reaches the terminal `errored` status. +Many storage- and capacity-related ingestion errors are **transient**: the pipeline retries them automatically with backoff, and they typically self-resolve within minutes. A code appearing in `error_code` does not by itself mean the item has failed permanently; only treat an item as a real failure once it reaches the terminal `errored` status. | Code | Meaning | Severity | |---|---|---| | `E6001` | Vector-store storage/indexing error while persisting processed data. The pipeline retries automatically and it usually clears within minutes. User message: *"Failed to store the processed data. Please try again. If the issue persists, contact support@hydradb.com."* | **Transient** (retryable) | -`E6001` is **transient**, not terminal. If you observe it on an in-flight item, keep polling [`/context/status`](/api-reference/v2/endpoint/source-status) - the item normally advances to `graph_creation` / `completed` on a subsequent retry with no action on your part. Only contact support if the item is still reported as `errored` after retries are exhausted. +`E6001` is **transient**, not terminal. If you observe it on an in-flight item, keep polling [`/context/status`](/api-reference/v2/endpoint/source-status): the item normally advances to `graph_creation` / `completed` on a subsequent retry with no action on your part. Only contact support if the item is still reported as `errored` after retries are exhausted. ## Retry pattern @@ -108,7 +106,7 @@ async function withRetry( } catch (error) { if (!(error instanceof HydraDBError)) throw error; - const retryable = [429, 500, 503].includes(error.statusCode); + const retryable = [429, 500, 503].includes(error.statusCode ?? 0); if (!retryable || attempt === maxRetries) throw error; const baseDelayMs = 2 ** attempt * 1000; @@ -259,7 +257,7 @@ Database creation is asynchronous. After `POST /databases`, poll [`GET /database - `forceful_relations.properties` has a nested value, an empty key or a reserved key, or is over 1 KiB. - A `graph_payload` key matches no `context_id` in the same request. - An `acl` entry is not a valid principal. -- The request exceeds the limits: 100 items, 1 MiB of text per item, 8 MiB of text per request, 1,024 bytes per `title`, 4,000 characters of `instructions`. +- The request exceeds the limits: 100 items, 1 MiB of text per item, 8 MiB of text per request, 1,024 bytes per `title`, 4,000 characters of `instructions`, 16 KiB of `attributes` or 1 KiB of `custom_attributes` per item. ### Empty query results @@ -271,6 +269,6 @@ Empty results are not always errors. Check these first: ## Related sections -- [API Reference](/api-reference/v2) - endpoint inventory and conventions -- [Ingestion Status](/api-reference/v2/endpoint/source-status) - async ingestion state -- [Query](/api-reference/v2/endpoint/query) - retrieval parameters and response shape +- [API Reference](/api-reference/v2): endpoint inventory and conventions +- [Ingestion Status](/api-reference/v2/endpoint/source-status): async ingestion state +- [Query](/api-reference/v2/endpoint/query): retrieval parameters and response shape diff --git a/api-reference/v2/index.mdx b/api-reference/v2/index.mdx index 5575add0..f856d249 100644 --- a/api-reference/v2/index.mdx +++ b/api-reference/v2/index.mdx @@ -16,9 +16,9 @@ description: "Single reference to all HydraDB endpoints" | Group | Purpose | When to reach for it | |---|---|---| -| [Databases](/api-reference/v2/endpoint/tenants-overview) | Create, monitor, and manage isolated workspaces | First step in any integration - and any time you need usage stats, provisioning status, or to tear down a workspace | +| [Databases](/api-reference/v2/endpoint/tenants-overview) | Create, monitor, and manage isolated workspaces | First step in any integration, and any time you need usage stats, provisioning status, or to tear down a workspace | | [Context](/api-reference/v2/endpoint/sources-overview) | Ingest, list, fetch, delete, and inspect context items | Every time data flows into HydraDB: text, conversations, and lifecycle ops | -| [Query](/api-reference/v2/endpoint/query-overview) | Retrieve context with hybrid or text query | At query time - the only endpoint you call to feed an LLM | +| [Query](/api-reference/v2/endpoint/query-overview) | Retrieve context with hybrid or text query | At query time: the only endpoint you call to feed an LLM | ## Core concepts @@ -148,8 +148,6 @@ Errors use the same envelope with `success: false`, `data: null`, and an `error` `meta` may also include a `deprecation` list when a request uses a legacy `/tenants` route or a deprecated field (`tenant_id`/`sub_tenant_id`, or `sub_tenant_ids` on `/query`); each entry carries `deprecated`, a `message`, and `deprecated_since` (field-level notices also add `deprecated_field` and `preferred_field`). It is a non-breaking migration nudge (the status code is unchanged) and is accompanied by a `Deprecation: true` response header. See [Migrating from `tenant_id` and `sub_tenant_id`](/essentials/v2/databases-and-collections#7-migrating-from-the-legacy-tenant-and-sub-tenant-fields). -- **Quick reference vs API details.** Each endpoint page starts with a short cheat sheet (what to send, what to save, common gotchas). Later on the page you will see a complete field reference with types, defaults, and examples that is kept in sync with the API. Use the cheat sheet to get moving quickly, and the API details when you need exact request/response shapes (especially for agents and strict validators). - - **Database scoping.** Most database-scoped endpoints require a `database` (formerly `tenant_id`). Many source and query endpoints also accept an optional `collection` (formerly `sub_tenant_id`) for finer-grained scoping. If omitted, the default collection is used. The old `tenant_id`/`sub_tenant_id` names (and the old `/tenants` routes) remain accepted as deprecated aliases; sending a canonical name and its alias with **different** values returns `400`. See [Migrating from `tenant_id` and `sub_tenant_id`](/essentials/v2/databases-and-collections#7-migrating-from-the-legacy-tenant-and-sub-tenant-fields). - **Async operations.** Database creation, deletion, and content ingestion are asynchronous. They return immediately after queuing. Use the relevant status endpoint to confirm completion before downstream operations. diff --git a/api-reference/v2/sdks.mdx b/api-reference/v2/sdks.mdx index ed7b42cc..a39e816e 100644 --- a/api-reference/v2/sdks.mdx +++ b/api-reference/v2/sdks.mdx @@ -46,7 +46,7 @@ const client = new HydraDBClient({ ## Versioning -The SDKs include `API-Version: 2` on every outbound request. The response carries an `X-API-Version: 2` header echoing the resolved version, so you can confirm which version a call used by inspecting the response headers. +The SDKs include `API-Version: 2` on every outbound request. Every response reports the API version that served it in `meta.api_version`. ## Naming conventions @@ -317,16 +317,16 @@ const result = await client.query({ maxResults: 8, }); -for (const chunk of result.data.chunks) { +for (const chunk of result.data?.chunks ?? []) { console.log(chunk.score, chunk.contextId, chunk.content); } -for (const path of result.data.graph) { +for (const path of result.data?.graph ?? []) { console.log(path.pathSummary); } // Inject the prompt-ready context into your model call. const messages = [ - { role: "system", content: result.data.llmPrompt }, + { role: "system", content: result.data?.llmPrompt }, { role: "user", content: question }, ]; ``` @@ -498,7 +498,7 @@ Both SDKs are fully typed: - **Autocomplete** for all method names and parameters - **Type checking** for request and response objects - **Inline documentation** for each parameter, sourced from the OpenAPI spec -- **Compile-time validation** for required vs optional fields +- **Static checking** of required versus optional fields - **Enum-typed values** for `query_by`, `operator` and `mode` @@ -574,9 +574,7 @@ Whether you're using TypeScript, Python, VS Code, PyCharm, or any modern IDE, th 1. Type the method name → see all available methods 2. Open the parentheses → see all required and optional parameters -3. Press `Cmd+Space` (macOS) or `Ctrl+Space` (Windows/Linux) → get inline documentation - -This works because the SDKs are fully typed with parameter docs sourced from the OpenAPI spec. +3. Hover a method or parameter → read its documentation ## Related sections diff --git a/continuity-assurance.mdx b/continuity-assurance.mdx index c9aff5f1..b53e3e5e 100644 --- a/continuity-assurance.mdx +++ b/continuity-assurance.mdx @@ -13,22 +13,22 @@ For certain clients, we have made contractual commitments to deploy HydraDB with ## Customer Base and Longevity -Our stability is reinforced by our expanding customer base. We're proud to support incredible companies, from emerging startups to established enterprises. Their trust not only solidifies HydraDB but also enhances its value for our broader ecosystem. +HydraDB serves a growing customer base, from early-stage startups to established enterprises. ## Flexible Deployment Options HydraDB offers various deployment choices to best suit your needs: -**HydraDB Cloud**: Hosted by us, ensuring ease of use and reliability. +**HydraDB Cloud**: Hosted and operated by us. -**HydraDB On-premises**: Hosted by you, with infrastructure management provided by us for optimal performance. +**HydraDB On-premises**: Hosted by you, with infrastructure management provided by us. -**HydraDB Self-hosted**: This option allows you to fully host and manage the solution, giving you the utmost control. Additionally, we offer access to HydraDB's source code, empowering your engineering team to build upon and customise our foundational technology according to your specific needs. +**HydraDB Self-hosted**: You host and manage HydraDB entirely yourself. We also offer access to HydraDB's source code, so your engineering team can build on it and customise it. ## Open-Source Commitment -**Why not open-source now?** Our pre-built AI search models demand significant time and attention to detail. Open-sourcing would require us to broadly distribute our intellectual property, and dedicate resources to building and managing a community - resources we wish to allocate judiciously. Maintaining our competitive advantage in creating superior AI search capabilities is also paramount. +**Why not open-source now?** Our pre-built AI search models demand significant time and attention to detail. Open-sourcing would require us to broadly distribute our intellectual property, and dedicate resources to building and managing a community, resources we wish to allocate judiciously. Maintaining our competitive advantage in creating superior AI search capabilities is also paramount. **Staying true to the open-source movement**: We believe that partially open-sourcing our product merely to label ourselves as 'open-source' contradicts the core principles of the open-source community. We are strong believers in the open-source movement and understand the importance of contributing to it with a perspective that extends beyond mere nomenclature. -If you have suggestions on what approach you think works great for core infrastructure products like HydraDB, we're all ears. Please write to us at [founders@hydradb.com](mailto:founders@hydradb.com) \ No newline at end of file +If you have suggestions on the right approach for core infrastructure products like HydraDB, write to us at [founders@hydradb.com](mailto:founders@hydradb.com). \ No newline at end of file diff --git a/essentials/v2/access-control.mdx b/essentials/v2/access-control.mdx index bf0f9476..a98e8fd0 100644 --- a/essentials/v2/access-control.mdx +++ b/essentials/v2/access-control.mdx @@ -3,7 +3,7 @@ title: "Access Control" description: "Restrict who can retrieve a document. Declare permissions yourself, or let connectors capture them from the source app." --- -By default every document in a collection is retrievable by every query against it. Access control changes that: a document can carry an **ACL** - a list of principals allowed to retrieve it - and a query can carry the identity it is running on behalf of. HydraDB returns only the documents that identity is allowed to see. +By default every document in a collection is retrievable by every query against it. Access control changes that: a document can carry an **ACL** (a list of principals allowed to retrieve it), and a query can carry the identity it is running on behalf of. HydraDB returns only the documents that identity is allowed to see. This is what you need to build an internal search product where a query by one employee must not surface a private Slack channel or a restricted Drive file belonging to another. @@ -31,18 +31,18 @@ A principal is one string identifying who may retrieve a document. Five forms: | Principal | Meaning | |---|---| | `user_email:grace@acme.com` | One person, by email. A bare `grace@acme.com` is accepted and normalized to this form. | -| `domain:acme.com` | Everyone whose email is under that domain. Matches automatically for any caller who queries with an email at that domain - you do not have to declare it on the query side. | +| `domain:acme.com` | Everyone whose email is under that domain. Matches automatically for any caller who queries with an email at that domain; you do not have to declare it on the query side. | | `group::` | A group in the source app, for example `group:slack:C0123` or `group:google:eng@acme.com`. | | `__public__` | Every identified caller in the collection. | -| `__private__` | Nobody. The stored form of an explicitly empty allow-list. | +| `__private__` | Nobody who queries with an `acl`. The stored form of an explicitly empty allow-list. | -Principals are lowercased, trimmed, and deduplicated on the way in. `__public__` overrides everything else in the same list - a document that is public is public. A list containing `__private__` alongside real principals keeps the real principals and drops the sentinel; `__private__` only means something on its own. +Principals are lowercased, trimmed, and deduplicated on the way in. `__public__` overrides everything else in the same list. A list containing `__private__` alongside real principals keeps the real principals and drops the sentinel; `__private__` only means something on its own. -**An absent ACL and an empty ACL are not the same thing.** A document with no `acl` is *unrestricted* - that is how every document ingested before you adopted access control behaves, and it is why adopting it never silently hides your existing content. A document you explicitly restrict to nobody is stored as `__private__`. Sending `"acl": []` means "nobody", not "everybody". +**An absent ACL and an empty ACL are not the same thing.** A document with no `acl` is *unrestricted*: that is how every document ingested before you adopted access control behaves, and it is why adopting it never silently hides your existing content. A document you explicitly restrict to nobody is stored as `__private__`. Sending `"acl": []` means "nobody", not "everybody". -Limits: 1000 principals per document, 256 characters per principal. Past that, use a `group:` or `domain:` principal - a list of several thousand individuals is organizational structure, not an allow-list. +Limits: 1000 principals per document, 256 characters per principal. Past that, use a `group:` or `domain:` principal; a list of several thousand individuals is organizational structure, not an allow-list. --- @@ -130,7 +130,7 @@ For supported providers, HydraDB reads the source app's own permissions on every | **Confluence** | Space-level view permissions, with groups expanded to member emails, plus per-page view restrictions. | | **Jira** | Who holds Browse access per project, plus per-issue security levels. | -Check what is live for your account with `GET /connector-catalog`: each provider carries `rbac_support` and a one-line `rbac_description` of what it captures. +The table covers the most common providers; others, such as Dropbox, Zendesk and Linear, also capture permissions. Check what is live for your account with `GET /connector-catalog`: each provider carries `rbac_support` and a one-line `rbac_description` of what it captures. Precedence, when both exist: @@ -139,7 +139,7 @@ Precedence, when both exist: - Otherwise the provider's resource-level verdict wins over your rule, because the provider is the fresher source of truth. -Capture is per provider and can be turned off without a deploy. A provider without capture is not broken - your own rules still work on it, and documents remain unrestricted until you set one. +Capture is enabled per provider. On a provider without capture, your own rules still work, and documents remain unrestricted until you set one. --- @@ -182,7 +182,7 @@ Send group principals explicitly when you want them: `"acl": ["grace@acme.com", | `"acl": ["__private__"]` | Public and unrestricted content only. | -An entry that is neither an email nor a recognized principal is kept as-is and matches nothing but public content. A typo narrows results; it never widens them. If a caller sees less than you expect, check the principal spelling first. +An entry that is neither an email nor a recognized principal is kept as-is and matches only public and unrestricted content. A typo narrows results; it never widens them. If a caller sees less than you expect, check the principal spelling first. Access control composes with, and is independent of, [attribute filters](/essentials/v2/attributes): filters express *what you are looking for*, ACLs express *what you are allowed to find*. A caller cannot widen their own visibility with a filter. @@ -221,7 +221,7 @@ A provider-side permission change is picked up on the next sync cycle for that r -Check the principal forms on both sides. `group:slack:C0123` on the document only matches a query that declares that same group; unlike `domain:`, group membership is not derived from the caller's email. Also confirm the email matches exactly - principals are compared after lowercasing and trimming, but not otherwise fuzzy-matched. +Check the principal forms on both sides. `group:slack:C0123` on the document only matches a query that declares that same group; unlike `domain:`, group membership is not derived from the caller's email. Also confirm the email matches exactly: principals are compared after lowercasing and trimming, but not otherwise fuzzy-matched. @@ -229,9 +229,9 @@ Check the principal forms on both sides. `group:slack:C0123` on the document onl ## Related -- [Connectors](/essentials/v2/connectors) - syncing app data, and per-resource ACL rules +- [Connectors](/essentials/v2/connectors): syncing app data, and per-resource ACL rules - [Ingest context](/essentials/v2/ingest#9-restricting-an-item): the `acl` item field -- [Query](/essentials/v2/query) - the `acl` field alongside every other retrieval parameter -- [Metadata](/essentials/v2/attributes) - filtering by attributes, a different question from permission -- [Multi-Tenant Support](/essentials/v2/databases-and-collections) - databases and collections, the isolation boundary ACLs work inside -- [Update Source Metadata - API Reference](/api-reference/v2/endpoint/update-source-metadata) - the `acl` replacement contract +- [Query](/essentials/v2/query): the `acl` field alongside every other retrieval parameter +- [Attributes](/essentials/v2/attributes): filtering by attributes, a different question from permission +- [Databases and collections](/essentials/v2/databases-and-collections): the isolation boundary ACLs work inside +- [Update Source Metadata API reference](/api-reference/v2/endpoint/update-source-metadata): the `acl` replacement contract diff --git a/essentials/v2/api-results.mdx b/essentials/v2/api-results.mdx index f2565896..bd36a856 100644 --- a/essentials/v2/api-results.mdx +++ b/essentials/v2/api-results.mdx @@ -126,8 +126,7 @@ FAQ: refunds to a card take 5 to 7 business days to appear. ## Related facts -- [P1] **Refund Processing** -managed by→ **Finance Department** (relevance 0.81) [1] - Refund processing is managed by the Finance Department. +- [P1] **Refund Processing** -managed by→ **Finance Department** [1] - [P2] **User** -prefers→ **short answers** (relevance 0.74) [2] The user prefers short answers about refunds. @@ -167,7 +166,7 @@ Render a UI, rerank, or apply your own rules from the three structured keys. The | `chunks[].content` | The matched text, verbatim. | | `chunks[].enrichment` | What enrichment extracted from that chunk (a preference, a fact), as a string. | | `chunks[].score` | Relevance, for your own thresholds. | -| `graph[].path_summary` | One sentence per graph path; `graph[].triplets` for the steps and `graph[].origin` for the lane that found it. To show a path under its chunk, see [Attaching graph paths to chunks](/essentials/v2/query#attaching-graph-paths-to-chunks). | +| `graph[].path_summary` | One sentence per graph path; `graph[].triplets` for the steps and `graph[].origin` for how it was found. To show a path under its chunk, see [Attaching graph paths to chunks](/essentials/v2/query#attaching-graph-paths-to-chunks). | | `forceful_relations[]` | Chunks linked at ingest with `forceful_relations`, each with the `via` link that brought it in. | diff --git a/essentials/v2/architecture.mdx b/essentials/v2/architecture.mdx index 2b096cdf..feb8f47f 100644 --- a/essentials/v2/architecture.mdx +++ b/essentials/v2/architecture.mdx @@ -3,7 +3,7 @@ title: "Architecture" description: "How HydraDB moves content from ingestion to indexed query, and where databases, metadata, graph context, and retrieval fit together." --- -HydraDB is context infrastructure for AI applications. From the outside, you call a small set of HTTP APIs. Inside, HydraDB orchestrates database isolation, asynchronous ingestion, indexing, graph construction, and hybrid retrieval - so your application can store context once and query the right pieces later. This page walks through what happens behind the scenes, and points at the endpoints and concepts you'll touch along the way. +HydraDB is context infrastructure for AI applications. From the outside, you call a small set of HTTP APIs. Inside, HydraDB orchestrates database isolation, asynchronous ingestion, indexing, graph construction, and hybrid retrieval, so your application can store context once and query the right pieces later. This page walks through what happens behind the scenes, and points at the endpoints and concepts you'll touch along the way. --- @@ -15,7 +15,7 @@ HydraDB organizes its work into three logical planes. You interact only with the |---|---|---| | **Control** | API authentication, database lifecycle, provisioning, and status | [`/databases`](/api-reference/v2/endpoint/tenants-overview) family | | **Ingestion** | Context item writes, connector syncs, parsing, chunking, embedding, and graph construction | [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context), [`GET /context/status`](/api-reference/v2/endpoint/source-status) | -| **Retrieval** | Hybrid query, metadata filtering, graph context, keyword bm25 search, and response shaping | [`POST /query`](/api-reference/v2/endpoint/query), [`GET /context/relations`](/api-reference/v2/endpoint/source-relations) | +| **Retrieval** | Hybrid query, attribute filtering, graph context, keyword (BM25) search, and response shaping | [`POST /query`](/api-reference/v2/endpoint/query), [`GET /context/relations`](/api-reference/v2/endpoint/source-relations) | ```mermaid flowchart LR @@ -125,7 +125,7 @@ The full status table and polling pattern lives at [Ingestion Status](/api-refer Here's the canonical end-to-end flow. Each step links to the endpoint that owns it: -1. **Create a database** with [`POST /databases`](/api-reference/v2/endpoint/create-tenant) - your isolated workspace, optionally with a [metadata schema](/essentials/v2/attributes) declared up front. +1. **Create a database** with [`POST /databases`](/api-reference/v2/endpoint/create-tenant): your isolated workspace, optionally with a [metadata schema](/essentials/v2/attributes) declared up front. 2. **Wait for provisioning** by polling [`GET /databases/status`](/api-reference/v2/endpoint/tenant-status) until `infra.ready_for_ingestion` is `true`. 3. **Ingest content** with [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context): `context` items, text or conversations, into a shared collection or a person's own. See [Ingest context](/essentials/v2/ingest) for every item field. 4. **Watch indexing finish** by polling [`GET /context/status`](/api-reference/v2/endpoint/source-status) until each `id` reaches `completed` (or `graph_creation` if you don't need graph traversal). @@ -137,7 +137,7 @@ That's the whole loop. Most of what changes between integrations is *what* you p ## Database isolation -A `database` (formerly `tenant_id`, still accepted as a deprecated alias) is the top-level isolation boundary - no database can read another database's data. Within a database, `collection` (formerly `sub_tenant_id`) carves out logical partitions: users, teams, workspaces, or projects. Omitting `collection` on any call resolves to the database's default collection, which is created on the first write - no collection exists until then. +A `database` (formerly `tenant_id`, still accepted as a deprecated alias) is the top-level isolation boundary: no database can read another database's data. Within a database, `collection` (formerly `sub_tenant_id`) carves out logical partitions: users, teams, workspaces, or projects. Omitting `collection` on any call resolves to the database's default collection, which is created on the first write; no collection exists until then. The right mapping depends on your product shape: @@ -157,8 +157,8 @@ The deeper trade-offs (when to spin up a new database vs. a new collection, how 1. **Authenticate and scope.** Validate `database`, resolve the database, and apply the requested `collection`. 2. **Filter before ranking.** Apply `attributes` to narrow the candidate set (see [Attributes](/essentials/v2/attributes)). -3. **Retrieve.** Run hybrid retrieval over the semantic vector store and the keyword bm25 index, or BM25-only retrieval when `query_by: "text"`. -4. **Blend.** Use `alpha` to weight semantic vs. keyword bm25 contributions (`1.0` = pure semantic, `0.0` = pure BM25). +3. **Retrieve.** Run hybrid retrieval over the semantic vector store and the keyword (BM25) index, or BM25-only retrieval when `query_by: "text"`. +4. **Blend.** Use `alpha` to weight semantic vs. keyword (BM25) contributions (`1.0` = pure semantic, `0.0` = pure BM25). 5. **Enrich.** With `graph_context` on (the default), traverse the [context graph](/essentials/v2/context-graphs) and attach related paths. When `mode: "thinking"`, expand the query, rerank, and pull in the relations items declared at ingest. 6. **Shape the response.** Return ranked `chunks`, graph paths in `graph`, declared links in `forceful_relations`, and `llm_prompt`, the same context as one prompt-ready string with citation labels. @@ -176,9 +176,9 @@ A short cheat sheet for the parameters you'll touch most often, and where each o | `collection` | Ingest + query | Narrows data to a user, team, or workspace inside the database. Use a single collection for writes and single-scope queries. | | `collections` | Query | Preferred query-time collection selector. Use a single-item list, a multi-scope list with equal weights, or a weighted object for fanout ranking. | | `attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Declared, filterable fields on an item. Must match the [database metadata schema](/essentials/v2/attributes) declared at database creation. | -| `custom_attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Free-form per-item fields. Stored with the item; not filterable. | +| `custom_attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Free-form per-item fields. Stored with the item; not filterable with `attributes`. | | `attributes` | [Query](/api-reference/v2/endpoint/query) | Deterministic narrowing with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on declared attributes. | -| `alpha` | [Query](/api-reference/v2/endpoint/query) | Blends semantic vs. keyword bm25 scores in `query_by: "hybrid"`. | +| `alpha` | [Query](/api-reference/v2/endpoint/query) | Blends semantic vs. keyword (BM25) scores in `query_by: "hybrid"`. | | `graph_context` | [Query](/api-reference/v2/endpoint/query) | On by default: returns relation paths from the [context graph](/essentials/v2/context-graphs) in `graph[]`. | | `mode` | [Query](/api-reference/v2/endpoint/query) | `"fast"` for low-latency single-pass retrieval; `"thinking"` for multi-query expansion and reranking. | @@ -189,19 +189,17 @@ A short cheat sheet for the parameters you'll touch most often, and where each o HydraDB separates **write-time** work from **query-time** work. Uploads return quickly and run in the background; query stays synchronous and reads only indexed content. When results look incomplete, walk the chain in order: 1. **Status first.** Did every `id` reach `completed` (or at least `graph_creation`)? Check [Ingestion Status](/api-reference/v2/endpoint/source-status). -2. **Scope second.** Is the `database` correct? Did you write under one `collection` and read under another? See [Multi-Tenant](/essentials/v2/databases-and-collections). -3. **Filters third.** Is the key declared? `attributes` filters only match fields declared in `database_metadata_schema` and sent as `attributes` at ingest; `custom_attributes` are never filterable. For hot filters, declare the field with `enable_match: true`. See [Attributes](/essentials/v2/attributes). - -This order catches almost every "I uploaded but query returns nothing" debugging session. +2. **Scope second.** Is the `database` correct? Did you write under one `collection` and read under another? See [Databases and collections](/essentials/v2/databases-and-collections). +3. **Filters third.** Is the key declared? `attributes` filters only match fields declared in `database_metadata_schema` and sent as `attributes` at ingest; `custom_attributes` cannot be filtered with `attributes`. See [Attributes](/essentials/v2/attributes). --- ## Related - [Core Concepts](/get-started/v2/core-concepts): the primitives, from databases and collections to the context graph -- [Quickstart](/get-started/v2/quickstart) - build your first integration in five minutes +- [Quickstart](/get-started/v2/quickstart): build your first integration in five minutes - [Ingest context](/essentials/v2/ingest): one database of context items, text or conversations -- [Query](/essentials/v2/query) - deep dive on `POST /query` -- [Multi-Tenant Support](/essentials/v2/databases-and-collections) - scoping patterns and pitfalls -- [Context Graphs](/essentials/v2/context-graphs) - how the graph layer enriches retrieval -- [Metadata](/essentials/v2/attributes) - designing filterable fields +- [Query](/essentials/v2/query): deep dive on `POST /query` +- [Databases and collections](/essentials/v2/databases-and-collections): scoping patterns and pitfalls +- [Context Graphs](/essentials/v2/context-graphs): how the graph layer enriches retrieval +- [Attributes](/essentials/v2/attributes): designing filterable fields diff --git a/essentials/v2/attributes.mdx b/essentials/v2/attributes.mdx index 374d4180..3a6b4590 100644 --- a/essentials/v2/attributes.mdx +++ b/essentials/v2/attributes.mdx @@ -10,7 +10,7 @@ An item carries two kinds: | Kind | Sent on an item as | Declared in the schema | Filterable at query time | Stored cap per item | | --- | --- | --- | --- | --- | | Attributes | `attributes` | Yes, in `database_metadata_schema` | Yes, with `attributes` on `POST /query` | 16 KiB | -| Custom attributes | `custom_attributes` | No | Never | 1 KiB | +| Custom attributes | `custom_attributes` | No | No, `attributes` cannot filter them | 1 KiB | If a field scopes your queries, declare it in the [database schema](/api-reference/v2/endpoint/create-tenant) and send it in `attributes`. If it is there for display, citations, debugging or an external ID, send it in `custom_attributes`. @@ -45,7 +45,7 @@ A query then filters on the declared fields: | --- | --- | --- | | Scope most queries by a field like department, region, plan, customer or status | `attributes` | Declare the field in `database_metadata_schema` and filter with `"attributes": { "department": "legal" }`. | | Filter by a number or a date range | `attributes` | Store a number, or a date string in one fixed format such as `YYYY-MM-DD`, and filter with `$gt`, `$gte`, `$lt`, `$lte`. | -| Keep source details like author, a Slack timestamp, an external ID or a document version | `custom_attributes` | No schema needed. Stored with the item, never filterable. | +| Keep source details like author, a Slack timestamp, an external ID or a document version | `custom_attributes` | No schema needed. Stored with the item, not filterable with `attributes`. | | Combine a hard scope with semantic search | `attributes` | Send the filter plus your natural-language `query`. The filter narrows the candidates; ranking still uses the query. | | Search semantically over a text attribute | `attributes`, on a `VARCHAR` field with `enable_dense_embedding: true` | Put the concept in `query`. Do not put fuzzy concepts in the filter. | | Search by keyword over a text attribute | `attributes`, on a `VARCHAR` field with `enable_sparse_embedding: true` | Normal `/query` keyword (BM25) matching covers it. | @@ -257,7 +257,7 @@ Rules checked before anything is queued: - When the database has a schema, every `attributes` key must be declared in it, and every value must match the declared type: a string for `VARCHAR`, `true` or `false` for `BOOL`, a whole number for the integer types, a number for `FLOAT` and `DOUBLE`, an object for `JSON`. `null` is accepted for any declared field. An undeclared key or a wrong type rejects the request with `400`. - `custom_attributes` take any keys, with no schema. - In both maps, keys must not start with `_`, must not be a reserved system name, and must not contain control characters. A value may be a scalar, a list or an object, but not a list or object nested inside another. -- Errors name the item they refer to, such as `context[0]: ...`. +- Structural and size errors name the item they refer to, such as `context[0]: ...`. **Attributes are set at ingest.** The `attributes` query filter runs against the values indexed with the item. To change them, re-ingest the item with `upsert: true` and the same `context_id`, which replaces the item. See [IDs and replacement](/essentials/v2/ingest#12-ids-and-replacement). @@ -276,7 +276,7 @@ The cap applies to the **whole map**, not to any one value, and it is measured o - **Keys and punctuation count.** Quotes, colons, commas and braces are all part of the payload that is measured. - **Bytes, not characters.** Accented Latin characters cost 2 bytes, most CJK characters 3, and emoji 4. -- **Budget in bytes from the start.** A 950-character summary sounds comfortably under a 1 KiB cap, but with two small sibling keys it serializes to 1,014 bytes: 64 bytes of that is structure alone. Push the summary to 1,000 characters and the request is rejected at 1,064 bytes. +- **Budget in bytes from the start.** A 950-character summary sounds comfortably under a 1 KiB cap, but with two small sibling keys it serializes to 1,014 bytes: 64 bytes of that is everything except the summary. Push the summary to 1,000 characters and the request is rejected at 1,064 bytes. ```json custom_attributes: 1,014 bytes, just inside the 1 KiB cap {"deck":"Q3 Board Deck","author":"ada@example.com","summary":"<950 characters>"} @@ -392,7 +392,7 @@ Operators combine and nest: | `JSON` fields | Only `$exists` applies. Any other operator on a `JSON` field is a `400`. | | Missing values | An item with no value for a field never matches a comparison on that field. `$ne`, `$nin` and `$not` exclude it too. To keep such items, say so: `{"$or": [{"region": {"$ne": "eu"}}, {"region": {"$exists": false}}]}`. | | Field names | Must be declared in `database_metadata_schema`. An undeclared field is a `400` (`unknown attribute`), never silently ignored. A reserved system column is a `400`. On a database created without any schema, every field is compared as a string. | -| Custom attributes | Cannot be filtered. Naming the custom attributes namespace inside `attributes` is a `400`. | +| Custom attributes | Cannot be filtered with `attributes`. Naming the custom attributes namespace inside `attributes` is a `400`. | | Empty pieces | An empty object, an empty operator object, or an empty `$and`, `$or`, `$in` or `$nin` array is a `400`, not a filter that matches everything. | | Unknown operators | A `400`. There is no `$contains`, `$regex` or fuzzy operator. | | Nesting | At most 10 levels deep through `$and`, `$or` and `$not`. | @@ -502,7 +502,7 @@ To page through items rather than run retrieval, use [`POST /context/list`](/api | --- | --- | --- | | Query returns `400 unknown attribute` | The field is not declared in `database_metadata_schema` | Declare it with `PATCH /databases/{database}/metadata-schema`, then re-ingest the items that should carry it. | | Ingest returns `400` naming an undeclared field | An `attributes` key is not in the schema | Declare the field, or move it to `custom_attributes` if you never filter on it. | -| A filter on a custom attribute is rejected | `custom_attributes` are never filterable | Declare the field, send it in `attributes`, and re-ingest. | +| A filter on a custom attribute is rejected | `attributes` cannot filter on `custom_attributes` | Declare the field, send it in `attributes`, and re-ingest. | | `400 value for "priority" does not match its type` | The operand's JSON type differs from the declared type, such as `"7"` for an `INT64` field | Send the declared type: `{"priority": 7}`. | | `$in` does not find an item whose field holds several values | There is no containment | See [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | | `$ne` or `$not` drops items that have no value for the field | Missing values never match a comparison | Add `{"field": {"$exists": false}}` under `$or`. | diff --git a/essentials/v2/bring-your-own-graph.mdx b/essentials/v2/bring-your-own-graph.mdx index 99d9bd99..24e2e068 100644 --- a/essentials/v2/bring-your-own-graph.mdx +++ b/essentials/v2/bring-your-own-graph.mdx @@ -89,8 +89,8 @@ In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, wh | Entities per item | ≤ 5,000 | | Relations per item | ≤ 10,000 | | Relations per entity (degree) | ≤ 500 | -| Relation `context` length | ≤ 2,000 characters | -| Entity key, `name`, `type`, `namespace`, `identifier`, `predicate` and `temporal_details` length | ≤ 256 characters each | +| Relation `context` length | ≤ 2,000 bytes (UTF-8) | +| Entity key, `name`, `type`, `namespace`, `identifier`, `predicate` and `temporal_details` length | ≤ 256 bytes (UTF-8) each | The request itself keeps the normal ingest limits: at most 100 items, 1 MiB of text per item and 8 MiB of text per request. See [Ingest context](/essentials/v2/ingest#limits-and-unrecognised-fields). @@ -255,7 +255,7 @@ await client.context.ingest({ Keys inside each item and each graph stay `snake_case` in every language (`context_id`, `temporal_details`); only the SDK's own arguments follow the language's casing. -Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) until each item reaches `completed` (or `graph_creation`), then query. `graph_context` is on by default: +Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) until each item reaches `completed`, so its graph is written, then query. `graph_context` is on by default: ```bash cURL curl -X POST 'https://api.hydradb.com/query' \ diff --git a/essentials/v2/connectors.mdx b/essentials/v2/connectors.mdx index 907a2455..4d183ed8 100644 --- a/essentials/v2/connectors.mdx +++ b/essentials/v2/connectors.mdx @@ -9,7 +9,7 @@ Connectors bring external app data into HydraDB automatically. Instead of ingest ## How it works -A connector runs three stages on every sync cycle: +A connector goes through three stages: ``` Discover → Configure → Sync @@ -41,7 +41,7 @@ curl -X POST 'https://api.hydradb.com/connectors' \ "database": "acme_corp", "collection": "engineering", "provider_account_scope": "T12345ACME", - "credentials": { "api_token": "xoxp-..." } + "credentials": { "access_token": "xoxp-..." } }' ``` @@ -49,8 +49,8 @@ curl -X POST 'https://api.hydradb.com/connectors' \ |---|---| | `provider` | A supported provider identifier returned by [`GET /connectors/providers`](#list-available-providers) | | `name` | Human-readable label for this connector | -| `tenant_id` | Which tenant receives the synced data | -| `sub_tenant_id` | Which sub-tenant partition receives the data | +| `database` | Which database receives the synced data | +| `collection` | Which collection receives the data | | `provider_account_scope` | Stable identifier for the external account. See below. | | `credentials` | Provider API token or access token | @@ -62,11 +62,11 @@ curl -X POST 'https://api.hydradb.com/connectors' \ | Provider | Value to use | Where to find it | |---|---|---| -| Slack | Workspace ID (starts with `T`) | Open Slack in a browser. The URL is `app.slack.com/client/TXXXXXXXX/...` - the `T...` segment is your workspace ID. | +| Slack | Workspace ID (starts with `T`) | Open Slack in a browser. The URL is `app.slack.com/client/TXXXXXXXX/...`; the `T...` segment is your workspace ID. | | GitHub | Organization or user login | The org or username in your GitHub URL: `github.com/my-github-org` | | Linear | Workspace name | Settings → Workspace → the name shown under your workspace | | Notion | Workspace name | Settings → Workspace → the name shown at the top | -| Gmail | Leave empty | Gmail does not use this field - use `account_email` in `additional_metadata` to scope by account instead | +| Gmail | Leave empty | Gmail does not use this field; use `account_email` in `additional_metadata` to scope by account instead | **Why it matters:** if you create two Slack connectors for different workspaces but give them the same `provider_account_scope` (or omit it on both), their synced messages share a deduplication namespace and will overwrite each other. Set a distinct value per connector whenever you connect more than one account of the same provider. @@ -86,7 +86,7 @@ curl -X POST 'https://api.hydradb.com/connectors/:id/configure' \ "resource_id": "C_GENERAL", "resource_type": "channel", "name": "general", - "sub_tenant_id": "all-hands", + "collection": "all-hands", "metadata": { "department": "all-hands" }, "additional_metadata": { "internal_label": "general-slack" } }, @@ -94,7 +94,7 @@ curl -X POST 'https://api.hydradb.com/connectors/:id/configure' \ "resource_id": "C_ENG", "resource_type": "channel", "name": "engineering", - "sub_tenant_id": "engineering", + "collection": "engineering", "metadata": { "department": "engineering" }, "additional_metadata": { "internal_label": "eng-slack" } } @@ -108,9 +108,9 @@ Each resource accepts the following optional fields: | Field | Purpose | |---|---| -| `sub_tenant_id` | Routes objects from this resource into a specific sub-tenant partition (overrides the connector-level `sub_tenant_id`) | -| `metadata` | Key-value pairs merged into tenant metadata on every synced object from this resource. Undeclared keys are accepted but only keys in `database_metadata_schema` are indexed for filtering. | -| `additional_metadata` | Key-value pairs merged into document metadata on every synced object from this resource | +| `collection` | Routes objects from this resource into a specific collection (overrides the connector-level `collection`) | +| `metadata` | Key-value pairs merged into the attributes of every synced object from this resource. Undeclared keys are accepted but only keys in `database_metadata_schema` are indexed for filtering. | +| `additional_metadata` | Key-value pairs merged into the custom attributes of every synced object from this resource | | `acl` | Restricts every object synced from this resource to the listed principals. Omitted means unrestricted. See [Access Control](/essentials/v2/access-control). | See [Metadata on synced objects](#metadata-on-synced-objects) for how these merge with system-generated fields. @@ -131,39 +131,39 @@ Each document is indexed as a `knowledge_base` object. Its markdown body is sear **From the dashboard:** tick "Workspace Documents" in the resource list, the same way you tick a team or project. -**From the API:** include the `linear_workspace` resource in your `configure` call. To put the documents in their own sub-tenant partition, set `sub_tenant_id` on it, the same as any other resource: +**From the API:** include the `linear_workspace` resource in your `configure` call. To put the documents in their own collection, set `collection` on it, the same as any other resource: ```json { "resource_id": "linear_workspace", "resource_type": "linear_workspace", - "sub_tenant_id": "linear-docs" + "collection": "linear-docs" } ``` -If you leave `sub_tenant_id` empty, the documents inherit the connector's sub-tenant partition. +If you leave `collection` empty, the documents inherit the connector's collection. --- ## Metadata on synced objects -Every object synced by a connector lands in HydraDB with two metadata layers: +Every object synced by a connector lands in HydraDB with two layers of metadata: -### Tenant metadata (`metadata`) +### Attributes (`metadata`) -Tenant metadata is the **schema-declared** layer. Fields here are defined once per tenant via `database_metadata_schema` and are indexed for fast, exact-match filtering. This is what you use for stable high-cardinality fields you filter on often - `department`, `region`, `status`, `priority`. +Attributes are the **schema-declared** layer. Fields are declared once per database in `database_metadata_schema` and indexed for exact-match filtering. Use them for fields you filter on often, such as `department`, `region`, `status` or `priority`. -HydraDB always writes `provider` into tenant metadata for every synced object. You can extend this with your own fields by passing `metadata` on each resource in `POST /connectors/:id/configure`. User-supplied fields are merged first; `provider` always takes precedence. +HydraDB always writes `provider` and `connector_id` into the attributes of every synced object. You can extend this with your own fields by passing `metadata` on each resource in `POST /connectors/:id/configure`. User-supplied fields are merged first; `provider` and `connector_id` always take precedence. -### Document metadata (`additional_metadata`) +### Custom attributes (`additional_metadata`) -Document metadata is the **free-form** layer. No schema required. Each connector automatically populates this with provider-specific fields on every synced object: connector ID, resource ID, provider account scope, and provider-native identifiers (Slack TS, GitHub issue number, Linear identifier, etc.). +Custom attributes are the **free-form** layer; no schema is required. Each connector populates them with provider-specific fields on every synced object: connector ID, resource ID, provider account scope, and provider-native identifiers (Slack TS, GitHub issue number, Linear identifier, etc.). You can extend this with your own fields by passing `additional_metadata` on each resource in `POST /connectors/:id/configure`. User-supplied fields are merged first; provider-generated fields always take precedence. This is what you filter on when you want to scope a query to a specific connector, channel, repo, or inbox. -```json Querying with document metadata filter +```json Querying with a custom attribute filter { "database": "acme_corp", "query": "deployment checklist", @@ -176,6 +176,8 @@ This is what you filter on when you want to scope a query to a specific connecto } ``` +`metadata_filters` is the older filter parameter. The [`attributes`](/essentials/v2/attributes) filter does not cover custom attributes, so scoping by connector, resource or account uses `metadata_filters`. + | Filter target | Key in `additional_metadata` | |---|---| | Specific connector | `connector_id` | @@ -186,8 +188,6 @@ This is what you filter on when you want to scope a query to a specific connecto ## Inspect what a connector stores -Connector contracts are provider-owned and available through the API for every supported connector. - ### List available providers `GET /connectors/providers` without a query parameter returns the catalog of connectable providers: @@ -219,7 +219,7 @@ curl 'https://api.hydradb.com/connectors/providers' \ | `provider` | Provider identifier. This is exactly the value accepted by the `id` parameter below and by the `provider` field in `POST /connectors` | | `category` | Display grouping for the provider | | `supported` | Whether the provider can be connected today | -| `moveit_support` | Whether the provider syncs through the MOVEIT pipeline | +| `moveit_support` | Which sync engine serves the provider. `credential_schema` already reflects it, so you do not need to read it | | `is_alpha` / `is_beta` | Maturity flags for the connector | | `rank` | Catalog display order (lower ranks first) | @@ -267,12 +267,12 @@ The response returns the provider identity, which provider streams get indexed, | Property | Meaning | |---|---| | `indexed_object_types` | The provider streams whose records become searchable documents | -| `searchable_fields` | Field values rendered into the indexed document text. Semantic and full-text queries find them as part of the document - they cannot be targeted individually | +| `searchable_fields` | Field values rendered into the indexed document text. Semantic and full-text queries find them as part of the document; they cannot be targeted individually | | `filterable_fields` | Keys that support exact-match filtering. Each entry carries `filter_key`, the literal key to pass inside a query's `metadata_filters` | | `credential_schema` | JSON Schema describing the credentials the provider needs to connect. Omitted if the schema source is unavailable | - You cannot pinpoint or search over a single searchable field. All `searchable_fields` are combined into one indexed document text, and search queries run over that combined text as a whole. To narrow results, use `filterable_fields` with `metadata_filters` - that is the only per-field targeting mechanism. + You cannot pinpoint or search over a single searchable field. All `searchable_fields` are combined into one indexed document text, and search queries run over that combined text as a whole. To narrow results, use `filterable_fields` with `metadata_filters`, the only per-field targeting mechanism. Each entry in `searchable_fields` and `filterable_fields` includes: @@ -281,10 +281,10 @@ Each entry in `searchable_fields` and `filterable_fields` includes: |---|---| | `name` | The normalized field or metadata key stored by HydraDB | | `data_type` | Its JSON type: `string`, `number`, `boolean`, `array`, `object`, or `null` | -| `filter_key` | Filterable fields only - the exact key to use in `metadata_filters` | +| `filter_key` | Filterable fields only: the exact key to use in `metadata_filters` | | `description` | Optional provider-specific context | -To scope a query with a filterable field, place its `filter_key` inside `metadata_filters`. A dotted key like `additional_metadata.container_id` nests under `additional_metadata`; tenant-scoped keys like `provider` and `connector_id` are passed top-level: +To scope a query with a filterable field, place its `filter_key` inside `metadata_filters`. A dotted key like `additional_metadata.container_id` nests under `additional_metadata`; attribute keys like `provider` and `connector_id` are passed top-level: ```json Filtering by a provider's filterable field { @@ -297,13 +297,13 @@ To scope a query with a filterable field, place its `filter_key` inside `metadat } ``` -```json Filtering by tenant-scoped keys +```json Filtering by attribute keys { "metadata_filters": { "provider": "slack" } } ``` -For classic providers, `credential_schema` is HydraDB's own contract - `slack` and `linear` each take a single `access_token`. For MOVEIT-synced providers it is the tap's schema - `dropbox` takes `app_key`, `app_secret`, and `refresh_token`. +`credential_schema` differs by provider: `slack` and `linear` each take a single `access_token`, while `dropbox` takes `app_key`, `app_secret`, and `refresh_token`. Connector contracts come from the same normalizers that prepare synced data. Query this endpoint instead of relying on a static field list: it covers the complete connector catalog and stays current as connector normalization changes. @@ -313,7 +313,7 @@ For Gmail, filter on `account_email` (filter key `additional_metadata.account_em ## Permissions on synced content -For supported providers, HydraDB reads the source app's permissions on every sync and applies them as document ACLs, so a query made on behalf of one user cannot surface a private channel, a restricted Drive file, or a repo they have no access to. Slack, Google Drive, GitHub, Confluence, and Jira have capture paths today; `GET /connector-catalog` reports which are live for your account. +For supported providers, HydraDB reads the source app's permissions on every sync and applies them as document ACLs, so a query made on behalf of one user cannot surface a private channel, a restricted Drive file, or a repo they have no access to. Capture paths exist for Slack, Google Drive, GitHub, Confluence, Jira and several other providers; `GET /connector-catalog` reports which are live for your account. You can also set your own rule per resource, either at configure time with the `acl` field above or afterwards, on its own: @@ -331,17 +331,17 @@ The change applies to every already-synced document from that resource on the ne ## Multiple connectors per provider -You can create more than one connector for the same provider - two Slack workspaces, two GitHub accounts, a personal and a work Gmail. Each connector is independent: its own credentials, its own resources, its own `provider_account_scope`. +You can create more than one connector for the same provider: two Slack workspaces, two GitHub accounts, a personal and a work Gmail. Each connector is independent: its own credentials, its own resources, its own `provider_account_scope`. -Set distinct `provider_account_scope` values per connector. This value is part of every object's deduplication key - without it, objects from two accounts of the same provider collide. +Set distinct `provider_account_scope` values per connector. This value is part of every object's deduplication key; without it, objects from two accounts of the same provider collide. -You can also route different resources from the same connector into different sub-tenants via `POST /connectors/:id/configure`: +You can also route different resources from the same connector into different collections via `POST /connectors/:id/configure`: ```json { "resources": [ - { "resource_id": "C_GENERAL", "name": "general", "sub_tenant_id": "all-hands" }, - { "resource_id": "C_ENG", "name": "engineering", "sub_tenant_id": "engineering" } + { "resource_id": "C_GENERAL", "name": "general", "collection": "all-hands" }, + { "resource_id": "C_ENG", "name": "engineering", "collection": "engineering" } ] } ``` @@ -350,8 +350,8 @@ You can also route different resources from the same connector into different su ## Related -- [Metadata](/essentials/v2/attributes) - tenant metadata vs document metadata in depth -- [Multi-Tenant](/essentials/v2/databases-and-collections) - routing resources to tenants and sub-tenants -- [Query](/essentials/v2/query) - querying connector-synced data with `query_apps: true` -- [Access Control](/essentials/v2/access-control) - restricting who can retrieve synced content +- [Attributes](/essentials/v2/attributes): attributes and custom attributes in depth +- [Databases and collections](/essentials/v2/databases-and-collections): routing resources to databases and collections +- [Query](/essentials/v2/query): querying connector-synced data with `query_apps: true` +- [Access Control](/essentials/v2/access-control): restricting who can retrieve synced content diff --git a/essentials/v2/context-graphs.mdx b/essentials/v2/context-graphs.mdx index b4ab5882..9c886536 100644 --- a/essentials/v2/context-graphs.mdx +++ b/essentials/v2/context-graphs.mdx @@ -43,7 +43,7 @@ Context graphs are hybrid: relationships are extracted at ingestion time and tra 1. HydraDB runs hybrid retrieval to find relevant chunks. 2. It traverses the graph from the query and from the retrieved chunks. -3. It returns the paths it found in `graph[]`: paths grown from the query first (`origin: "query_path"`), then paths expanded from the returned chunks (`origin: "chunk_relation"`). The list is deduplicated across both lanes, so a path both found appears once, and it is not capped. +3. It returns the paths it found in `graph[]`: paths grown from the query first (`origin: "query_path"`), then paths expanded from the returned chunks (`origin: "chunk_relation"`). The list is deduplicated across both origins, so a path found both ways appears once, and it is not capped. When no relevant relationships are found, `graph` is `[]`. That is not an error; it is the absence of structure for that query. @@ -51,7 +51,7 @@ When no relevant relationships are found, `graph` is `[]`. That is not an error; ## 5. Key concepts -**Triplets.** The unit of the graph. `source` and `target` are entities, `{ entity_id, name }`. `relation` describes the connection: `predicate`, the sentence it was extracted from (`context`), when it holds (`temporal_details`, omitted when empty), the edge's `timestamp` in Unix epoch seconds (a float, omitted when the edge has none), its `relationship_id`, and the `chunk_id` of the chunk that is evidence for it. +**Triplets.** The unit of the graph. `source` and `target` are entities, `{ entity_id, name }`. `relation` describes the connection: `predicate`, the sentence it was extracted from (`context`), when it holds (`temporal_details`, omitted when empty), when the relation was introduced (`timestamp`, Unix epoch seconds, omitted when the edge has none), its `relationship_id`, and the `chunk_id` of the chunk that is evidence for it. Example: `Alex`, `prefers`, `short answers`, from chunk `ck_9f2`. @@ -129,7 +129,7 @@ The `graph` key of the response looks like: "relation": { "predicate": "governs", "context": "The billing policy governs how failed payments are retried.", - "timestamp": 1782984600.0, + "timestamp": 1782984600, "relationship_id": "rel_41", "chunk_id": "ck_2aa" }, @@ -157,13 +157,12 @@ The `graph` key of the response looks like: ## 7. Using graph context in your prompt -You do not format the graph yourself. The `llm_prompt` returned by the same query already contains a `## Related facts` section with one line per path in `graph[]`: its label (`[P1]`, `[P2]`, ... in `graph[]` order, so an agent can cite a fact by it), its chain of hops, the path's relevance after reranking in parentheses when it has one (a path with no reranked score has no parenthetical), and the numbers of the results its hops were extracted from. The line does not say which lane found the path; read `graph[].origin` for that. The `path_summary` is indented on the line under it, unless it only narrates the hops the chain already shows. From the [example response on Query](/essentials/v2/query#1-one-call): +You do not format the graph yourself. The `llm_prompt` returned by the same query already contains a `## Related facts` section with one line per path in `graph[]`: its label (`[P1]`, `[P2]`, ... in `graph[]` order, so an agent can cite a fact by it), its chain of hops, the path's relevance after reranking in parentheses when it has one (a path with no reranked score has no parenthetical), and the numbers of the results its hops were extracted from. The line does not say how the path was found; read `graph[].origin` for that. The `path_summary` is indented on the line under it, unless it only narrates the hops the chain already shows. From the [example response on Query](/essentials/v2/query#1-one-call): ```markdown ## Related facts -- [P1] **Refund Processing** -managed by→ **Finance Department** (relevance 0.81) [1] - Refund processing is managed by the Finance Department. +- [P1] **Refund Processing** -managed by→ **Finance Department** [1] - [P2] **User** -prefers→ **short answers** (relevance 0.74) [2] The user prefers short answers about refunds. ``` @@ -192,7 +191,7 @@ Inject `llm_prompt` and the model can reason over the paths and cite them. See [ **Looking for forceful relations in `graph[]`.** Items you linked with `forceful_relations` come back in `forceful_relations[]`, with the `via` link that brought them in. -**Expecting relationships that do not exist.** If the retrieved chunks do not share entities or declared relations, `graph` is `[]`. +**Expecting relationships that do not exist.** If nothing in the graph connects the query and the retrieved chunks, `graph` is `[]`. --- diff --git a/essentials/v2/databases-and-collections.mdx b/essentials/v2/databases-and-collections.mdx index 20dc7b67..df529581 100644 --- a/essentials/v2/databases-and-collections.mdx +++ b/essentials/v2/databases-and-collections.mdx @@ -4,7 +4,7 @@ description: "How HydraDB scopes data using databases and collections, and how s --- - **Knowledge and memory (split databases) are deprecated.** Unified is the way to go, and you do not pass anything to get it: every database you create is unified. Send `context` items with [Ingest context](/essentials/v2/ingest) and read them back with [Query](/essentials/v2/query). + **Knowledge and memory (split databases) are deprecated.** Every database you create is unified, and you do not pass anything to get it. Send `context` items with [Ingest context](/essentials/v2/ingest) and read them back with [Query](/essentials/v2/query). @@ -50,8 +50,8 @@ Do not use `collection` as a substitute for separate production and staging data Use one database for the application or customer account, and use each end-user as a collection. ```text -database = "acme_app" -collection = "user_123" +database = "acme_app" +collection = "user_123" ``` Use this when each user has private context, preferences, or conversation history. @@ -67,8 +67,8 @@ Typical flow: Use one database per customer organization. Use `collection` for the workspace, team, project, or user scope inside that customer. ```text -database = "acme_corp" -collection = "workspace_42" +database = "acme_corp" +collection = "workspace_42" ``` Typical flow: @@ -111,16 +111,10 @@ Examples: Query requests include `database`, and may include `collection`. If `collection` is omitted, query uses the database's default collection. -Use the same scoping values on query that you used when writing the data. A query request with one `collection` should not be expected to retrieve data written under a different `collection`. - If your application needs to combine data from multiple scopes, prefer one query call with `collections` unless you need separate response formatting or client-side treatment per scope. Use `collection` for partitioning data. Use `attributes` for narrowing results within that scope. -### Personal and shared context - -A person's preferences and the company's shared context live in the same database and are read by the same `POST /query`; collections are what keep them apart. Write each person's context under their own `collection` and shared context under a shared one, and name both in `collections` when you query. Use two separate calls only when you need to format the two streams differently in your LLM prompt; otherwise one call and its `llm_prompt` is the whole pattern. - Use the same collection on writes and reads for the same logical scope.

Data written under one collection should not be expected to appear when querying from another. Keep scope identifiers stable and consistent in your application.

@@ -288,6 +282,7 @@ When a request uses a legacy route **or** a legacy field, HydraDB adds a non-bre "error": null, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", + "api_version": "2.0.1", "latency_ms": 12.3, "deprecation": [ { @@ -314,6 +309,6 @@ If you send **both** a canonical field and its deprecated alias: ## Related - [Ingest context](/essentials/v2/ingest): writing items into a collection -- [Query](/essentials/v2/query) - how scoping is applied at query time -- [How to Use API Results](/essentials/v2/api-results) - merging query results into a prompt -- [Create Database](/api-reference/v2/endpoint/tenants-overview) - defining databases and their metadata schema +- [Query](/essentials/v2/query): how scoping is applied at query time +- [How to Use API Results](/essentials/v2/api-results): merging query results into a prompt +- [Create Database](/api-reference/v2/endpoint/tenants-overview): defining databases and their metadata schema diff --git a/essentials/v2/glossary.mdx b/essentials/v2/glossary.mdx index 06a12e10..a1ea0f04 100644 --- a/essentials/v2/glossary.mdx +++ b/essentials/v2/glossary.mdx @@ -13,7 +13,7 @@ workspace. Data in one database is fully separated from every other. A partition *within* a database, typically one per end-user, team, or agent. Omit it and HydraDB uses the database's default collection. -See [Multi tenancy](/essentials/v2/databases-and-collections) for how scoping affects writes +See [Databases and collections](/essentials/v2/databases-and-collections) for how scoping affects writes and reads. ## Context item @@ -35,14 +35,14 @@ separately from the ranked `chunks` and the `graph` paths. See ## Deprecated aliases `database` and `collection` were previously called `tenant_id` and -`sub_tenant_id`. Wherever you meet an old name - a request field, a route, a -webhook payload - it is a deprecated alias, and it keeps working. +`sub_tenant_id`. Wherever you meet an old name (a request field, a route, a +webhook payload), it is a deprecated alias, and it keeps working. One exception: in the **indexing webhook payload**, `tenant_id` and `database` do NOT carry the same value. `database` is the name you ingested into; `tenant_id` is -an identifier for it. Everywhere else - request fields, routes, query -parameters - they remain interchangeable. See +an identifier for it. Everywhere else (request fields, routes, query +parameters) they remain interchangeable. See [Webhooks](/essentials/v2/webhooks#payload). @@ -54,6 +54,6 @@ parameters - they remain interchangeable. See | `/tenants/…` routes | `/databases/…` routes | Prefer the canonical names in new integrations. See -[Multi tenancy](/essentials/v2/databases-and-collections#7-migrating-from-the-legacy-tenant-and-sub-tenant-fields) +[Databases and collections](/essentials/v2/databases-and-collections#7-migrating-from-the-legacy-tenant-and-sub-tenant-fields) for the full compatibility contract, and [Webhooks](/essentials/v2/webhooks) for the delivery payload. diff --git a/essentials/v2/graph-collections-byog.mdx b/essentials/v2/graph-collections-byog.mdx index 36140740..87951468 100644 --- a/essentials/v2/graph-collections-byog.mdx +++ b/essentials/v2/graph-collections-byog.mdx @@ -1,7 +1,7 @@ --- title: "Bring Your Own Graph (BYOG)" sidebarTitle: "Cypher graph collections" -description: "BYOG - full Cypher access to graph collections you own end-to-end." +description: "BYOG: full Cypher access to graph collections you own end-to-end." --- Bring Your Own Graph (BYOG) gives you full **Cypher** access to graph @@ -13,9 +13,9 @@ their Cypher and their data model as-is. - **Databases** group your collections. A BYOG database appears in your dashboard and in the standard database APIs like any other HydraDB database. - **Collections** are independent graphs inside a database. Queries run - against exactly one collection - collections never see each other's data. -- **Full Cypher support**: reads and writes alike - `CREATE`, `MERGE`, - `MATCH`, `SET`, `DELETE` - plus the graph-native surface: multi-hop and + against exactly one collection; collections never see each other's data. +- **Full Cypher support**: reads and writes alike (`CREATE`, `MERGE`, + `MATCH`, `SET`, `DELETE`), plus the graph-native surface: multi-hop and variable-length traversal, relationship expansion, and shortest-path finding. Your query is sent verbatim; HydraDB never rewrites it. - **Isolation is structural.** Each collection is a completely separate graph @@ -28,7 +28,7 @@ their Cypher and their data model as-is. BASE=https://api.hydradb.com KEY= -# 1. Create a database (ready immediately) +# 1. Create a database curl -X POST "$BASE/byog/databases" \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"database": "crm"}' @@ -62,20 +62,23 @@ Every request needs your HydraDB API key: Authorization: Bearer ``` -A missing or invalid key returns `403`. Databases are scoped to the -organization that owns the API key - another organization's database names +A missing or invalid key returns `401`. Databases are scoped to the +organization that owns the API key: another organization's database names are invisible to you (they behave exactly like names that don't exist). ## Endpoints -### `POST /byog/databases` - create a database +### `POST /byog/databases`: create a database ```json { "database": "crm" } ``` -Returns immediately with `{"database": "crm", "status": "ready"}` - there is -no provisioning wait. Creating a name that already exists returns `409`. +Returns `{"database": "crm", "status": "ready", "cluster": "shared"}`, ready to +query. If your organization has a dedicated graph cluster, `status` is +`"provisioning"` and `cluster` is `"dedicated"` until that cluster is up; queries +meanwhile return `503` with a `Retry-After` header. Creating a name that already +exists returns `409`. The database also shows up everywhere your other HydraDB databases do: `GET /databases` lists it, `GET /databases/status` reports it ready, @@ -83,7 +86,7 @@ The database also shows up everywhere your other HydraDB databases do: the dashboard. Deleting it through the standard `DELETE /databases` flow removes its graph collections as well. -### `POST /byog/query` - run Cypher +### `POST /byog/query`: run Cypher ```json { @@ -94,22 +97,22 @@ removes its graph collections as well. } ``` -- **Collections auto-create** - there is no create-collection call. The first +- **Collections auto-create**: there is no create-collection call. The first write brings the collection into existence; reading a collection you never - wrote to simply returns zero rows. + wrote to returns zero rows. - Collection names must match `^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$`. Database names have no charset restriction. - Always pass user data through `params` rather than string-building it into - the query - parameters are bound safely and keep query plans cacheable. + the query; parameters are bound safely and keep query plans cacheable. - Request bodies are capped at **256 KiB** (`413` beyond that). For bulk loads, send batches of rows with `UNWIND $rows AS row CREATE ...`. -### `GET /byog/collections?database=crm` - list collections +### `GET /byog/collections?database=crm`: list collections Returns the collection names that exist in the database. An unknown database returns `404`. -### `DELETE /byog/collections` - drop one collection +### `DELETE /byog/collections`: drop one collection ```json { "database": "crm", "collection": "contacts" } @@ -118,14 +121,14 @@ returns `404`. Drops the collection and all its data. Deleting a collection that does not exist is a success (idempotent). -### `DELETE /byog/databases` - drop a database +### `DELETE /byog/databases`: drop a database ```json { "database": "crm" } ``` -Drops every collection in the database, and - if the database was created -through `POST /byog/databases` - removes the database itself. The response +Drops every collection in the database, and, if the database was created +through `POST /byog/databases`, removes the database itself. The response lists what was removed: ```json @@ -134,75 +137,70 @@ lists what was removed: If the database was created through the standard database API (and merely has graph collections in it), only the collections are dropped and `deleted` is -`false` - manage the database itself through the standard `DELETE /databases`. +`false`; manage the database itself through the standard `DELETE /databases`. ## Supported Cypher - **Coverage in one line:** essentially all of openCypher is supported - only - server-side procedures and file loading are excluded - which is the entire - surface a real application needs for modelling, loading, querying and - maintaining its own graph. + **Coverage in one line:** openCypher reads and writes are supported, except + procedure calls and `LOAD CSV`, with the few dialect differences listed below. -Your query is executed **verbatim** - HydraDB never rewrites it. "CRUD" is the -floor, not the ceiling: two kinds of work are fully supported. +Two kinds of work are supported. -**Modelling and CRUD** - the day-to-day read/write surface: pattern matching, +**Modelling and CRUD**: the day-to-day read/write surface: pattern matching, aggregation, `UNWIND`, `WITH` pipelines, `MERGE`, indexes (`CREATE INDEX FOR (n:Label) ON (n.prop)`), and `CALL { ... }` subqueries. -**Graph traversal and exploration** - the part that makes this a graph rather -than a table. You are not limited to reading and writing single nodes; you can -walk relationships to arbitrary depth, expand a node's neighborhood, follow +**Graph traversal and exploration**: walk relationships to arbitrary depth, expand a node's neighborhood, follow chains of edges, and find paths between nodes: -- **Multi-hop patterns** - chain relationships across as many hops as you need +- **Multi-hop patterns**: chain relationships across as many hops as you need in one `MATCH`: `MATCH (a:Person)-[:KNOWS]->(b)-[:WORKS_AT]->(c:Company) RETURN c.name`. -- **Variable-length traversal** - follow a relationship an unbounded or bounded +- **Variable-length traversal**: follow a relationship an unbounded or bounded number of hops with `*`: `MATCH (a:Person {name:$n})-[:KNOWS*1..4]->(reach) RETURN DISTINCT reach.name` returns everyone within four degrees. -- **Neighborhood expansion** - pull a node's edges and neighbors in a single +- **Neighborhood expansion**: pull a node's edges and neighbors in a single query, in any direction: `MATCH (p:Person {name:$n})-[r]-(nbr) RETURN type(r) AS rel, nbr.name AS neighbor`. -- **Path finding** - `shortestPath` returns the actual path - nodes and edges - in traversal order, not just the two endpoints. See +- **Path finding**: `shortestPath` returns the actual path (nodes and edges + in traversal order), not just the two endpoints. See [Relationships and paths](#relationships-and-paths). -- **Directed, typed, filtered traversal** - restrict to outgoing (`->`), +- **Directed, typed, filtered traversal**: restrict to outgoing (`->`), incoming (`<-`), or either (`-`) edges, filter by relationship type (`[:KNOWS]`), and constrain node or edge properties anywhere along the walk. Two constructs are **rejected**. "Rejected" means the query is refused *before it runs*: the whole request fails with a `400` and a message -explaining the reason, and **nothing is executed** - no partial writes, no +explaining the reason, and **nothing is executed**: no partial writes, no side effects. It is a validation error, not a runtime one, so retrying the same query fails identically until you change it. The following constructs always return `400` and are never executed: - - **Procedure calls** - `CALL some.procedure(...)`. Procedures are + - **Procedure calls**: `CALL some.procedure(...)`. Procedures are engine-specific internals that HydraDB does not commit to supporting. (`CALL { ... }` *subqueries* are fine.) - - **`LOAD CSV`** - server-side file/URL loading. Send data through `params` + - **`LOAD CSV`**: server-side file/URL loading. Send data through `params` instead. -A few dialect notes (each verified against the live service): +A few dialect notes: -- **Existence checks** are written as bare pattern predicates - +- **Existence checks** are written as bare pattern predicates: `MATCH (p:Person) WHERE (p)-[:KNOWS]->() RETURN p.name AS name`. The `EXISTS { ... }` block form and the `exists()` function are not accepted. - **`shortestPath`** goes in a `RETURN` or `WITH` clause (not `MATCH p = …`) - and the traversal must be directed - see the paths example above. + and the traversal must be directed; see the shortest-path example below. ### Traversal examples Expand a node's relationships, follow chains of edges, and find the shortest -path between two nodes - all in plain Cypher: +path between two nodes, all in plain Cypher: ```cypher Expand a node's neighborhood -- Every relationship on Alice and the node on the other end, in any direction. @@ -239,15 +237,15 @@ Successful calls return the standard HydraDB envelope: ```json { "success": true, - "data": [ ... ], + "data": [ { "name": "Alice", "role": "admin" } ], "error": null, - "meta": { "request_id": "9be86a4e-…", "latency_ms": 12.4 } + "meta": { "request_id": "9be86a4e-…", "api_version": "2.0.1", "latency_ms": 12.4 } } ``` For `POST /byog/query`, `data` is always a JSON **array of row objects**, one per result row, keyed by your `RETURN` column names. Unaliased expressions use -the expression text as the key - **alias everything you plan to parse** +the expression text as the key; **alias everything you plan to parse** (`RETURN n.name AS name`). A pure write with no `RETURN` yields `data: []`. ### How values are rendered @@ -255,14 +253,14 @@ the expression text as the key - **alias everything you plan to parse** | Cypher value | JSON | |---|---| | string / boolean / null | JSON string / boolean / null | -| integer | JSON number. Graph integers are 64-bit; values beyond 2⁵³ lose precision in languages that parse numbers as doubles - keep your own ids inside the safe range, or return them as strings | +| integer | JSON number. Graph integers are 64-bit; values beyond 2⁵³ lose precision in languages that parse numbers as doubles; keep your own ids inside the safe range, or return them as strings | | float | JSON number | | list / map | JSON array / object (rendered recursively) | | **node** | object with all node properties, plus `id` and `labels` | | **relationship** | object with all relationship properties, plus `id`, `relation`, `source_node_id`, `target_node_id` | | **path** | `{ "nodes": [...], "edges": [...] }` in traversal order | -Example - `RETURN n` where `n` is a node: +Example: `RETURN n` where `n` is a node: ```json { "data": [ { "n": { "id": 0, "labels": ["Person"], "name": "Alice", "age": 34 } } ] } @@ -272,21 +270,21 @@ Two things to know about ids: - The `id` / `labels` / `relation` / `source_node_id` / `target_node_id` keys are added by the renderer. If you store a property with one of those names, - it will be shadowed in the *response* (the stored value is unaffected) - + it will be shadowed in the *response* (the stored value is unaffected); avoid those property names or alias explicitly (`RETURN n.id AS my_id`). -- `id` values are internal and stable only within the life of a collection - +- `id` values are internal and stable only within the life of a collection: they can be reused after deletions and do not survive an export/re-import. Key your application on a property you own. ## Using results in your code -The patterns below are everything you need to consume query results reliably. -They're shown in Python and TypeScript; the ideas port to any language. +The client is shown in Python and TypeScript, the patterns after it in Python; +the ideas port to any language. ### A minimal client -Wrap the endpoint once and everything else becomes one-liners. Note the two -response shapes: success puts rows in `data`, errors are wrapped in `detail`. +Wrap the endpoint once. Success puts rows in `data`; an error carries its code +and message in `error`, repeated under `detail`. ```python import requests @@ -323,7 +321,7 @@ async function query(cypher: string, params: object = {}): Promise`): @@ -380,7 +378,7 @@ rows = g.query(""" hops = [n["name"] for n in rows[0]["p"]["nodes"]] # ["Alice", ..., "Bob"] ``` -### Pagination - the loop to copy +### Pagination: the loop to copy Result sets past the deployment cap are silently truncated, so any read that *could* be large should page. A stable `ORDER BY` makes pages consistent: @@ -399,7 +397,7 @@ def all_rows(cypher_body, page=500, params=None): people = list(all_rows("MATCH (p:Person) RETURN p.name AS name ORDER BY name")) ``` -### Bulk loading - the loop to copy +### Bulk loading: the loop to copy Chunk rows to stay inside the 256 KiB body cap and the 30 s write budget; `MERGE` on your own key makes the load re-runnable after a failure: @@ -416,44 +414,53 @@ def load(rows, chunk=500): ### Handling failures -- **`400`** - the message tells you what to fix: your Cypher (compiler +- **`400`**: the message tells you what to fix: your Cypher (compiler feedback is passed through) or a query that needs `LIMIT`/an index (budget timeout). Retrying unchanged will fail identically. -- **`429` / `500`** - transient; retry with backoff. Writes built on `MERGE` +- **`429` / `500`**: transient; retry with backoff. Writes built on `MERGE` (as above) are safe to retry; bare `CREATE` batches are not idempotent, so - a retried chunk can duplicate nodes - one more reason to key on your own id. -- A write with no `RETURN` succeeds with `data: []` - don't treat empty as + a retried chunk can duplicate nodes, one more reason to key on your own id. +- A write with no `RETURN` succeeds with `data: []`; don't treat empty as failure. ## Errors -Errors use HydraDB's structured error shape: +Errors use the standard envelope, with the code and message repeated under +`detail`: ```json -{ "detail": { "success": false, "message": "…", "error_code": "…" } } +{ + "success": false, + "data": null, + "error": { "code": "DATABASE_NOT_FOUND", "message": "…" }, + "meta": { "request_id": "9be86a4e-…", "api_version": "2.0.1", "latency_ms": 3.1 }, + "detail": { "success": false, "message": "…", "error_code": "DATABASE_NOT_FOUND" } +} ``` | Status | Meaning | |---|---| | `400` | Invalid request (missing fields, bad collection name), unsupported construct, **Cypher errors** (the compiler's message is passed through so you can fix the query), or **query timeout** | -| `403` | Missing or invalid API key | -| `404` | Unknown database - create it with `POST /byog/databases` | +| `401` | Missing or invalid API key | +| `403` | The API key's scope does not permit this operation | +| `404` | Unknown database; create it with `POST /byog/databases` | | `409` | `POST /byog/databases` with a name that already exists | | `413` | Request body over 256 KiB | -| `429` | Rate limit exceeded - back off and retry | -| `500` | Something failed on our side - safe to retry; nothing for you to fix | +| `429` | Rate limit exceeded; back off and retry | +| `500` | Something failed on our side; safe to retry, nothing for you to fix | +| `503` | The database's dedicated cluster is still provisioning; retry after the `Retry-After` interval | ## Limits & timeouts | Limit | Value | On exceeding | |---|---|---| | Request body | 256 KiB | `413` | -| Read query execution | 8 s | `400` - "query exceeded the execution time budget; simplify it, add LIMIT, or create an index" | +| Read query execution | 8 s | `400`: "query exceeded the execution time budget; simplify it, add LIMIT, or create an index" | | Write query execution | 30 s | same `400` | -| Result set size | deployment-configured cap; rows beyond it are **silently dropped** | no error - paginate | +| Result set size | deployment-configured cap; rows beyond it are **silently dropped** | no error; paginate | A query counts as a **write** (and gets the larger budget) when it contains -any write clause - `CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`, `FOREACH`. +any write clause: `CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`, `FOREACH`. Practical guidance: @@ -462,8 +469,8 @@ Practical guidance: at the result-set cap are arbitrary. - **Chunk bulk imports** into `UNWIND $rows` batches sized to finish inside the 30 s write budget (and the 256 KiB body cap). -- **Create indexes** for properties you filter on - - `CREATE INDEX FOR (n:Person) ON (n.name)` - long-running reads are usually +- **Create indexes** for properties you filter on + (`CREATE INDEX FOR (n:Person) ON (n.name)`); long-running reads are usually missing one. ## Migrating from Neo4j @@ -471,10 +478,10 @@ Practical guidance: Most application Cypher ports directly. The differences you are most likely to notice: -- `CALL db.*` / `CALL apoc.*` procedures are not available - the equivalents +- `CALL db.*` / `CALL apoc.*` procedures are not available; the equivalents are either plain Cypher or not part of the supported surface. -- `LOAD CSV` is not available - batch data in through `params`. -- Internal node ids are not portable (true in Neo4j as well) - migrate using +- `LOAD CSV` is not available; batch data in through `params`. +- Internal node ids are not portable (true in Neo4j as well); migrate using your own key properties, e.g. `UNWIND $rows AS row MERGE (n:Person {ext_id: row.ext_id}) SET n += row`. diff --git a/essentials/v2/ingest.mdx b/essentials/v2/ingest.mdx index e2736896..0dd6d0e3 100644 --- a/essentials/v2/ingest.mdx +++ b/essentials/v2/ingest.mdx @@ -151,7 +151,7 @@ Each item is exactly one of `text` or `conversation`. | Field | Notes | | --- | --- | -| `context_id` | Your id for the item. Generated when omitted (from `title`, so two items with the same text, no title and no id collide). Must not contain commas. | +| `context_id` | Your id for the item. Generated from the item's text and `title` when omitted, so two items with the same text and title and no id collide. Must not contain commas. | | `title` | Optional readable name. Searchable with `titles` on [query](/essentials/v2/query). At most 1,024 bytes. | | `text` | Plain text. Shape A. See [Text items](#4-text-items). | | `conversation` | A list of `{ role, content }` turns; roles are `user`, `assistant` and `system`. Shape B. See [Conversation items](#5-conversation-items). | @@ -160,7 +160,7 @@ Each item is exactly one of `text` or `conversation`. | `instructions` | Steer enrichment for this item. At most 4,000 characters. Default: the request's `instructions`. | | `happened_at` | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. See [Attributes](/essentials/v2/attributes). | -| `custom_attributes` | Free-form fields. Not filterable. | +| `custom_attributes` | Free-form fields. Not filterable with `attributes`. | | `forceful_relations` | Relations you declare to other items: `{ "context_ids": ["chat-w1"], "properties": {} }`, where `context_ids` are the `context_id`s of the related items. See [Declared relations](#10-declared-relations). | | `acl` | Principals allowed to retrieve the item, such as `user_email:a@x.com` or `domain:acme.com`. Omit for unrestricted, `[]` for nobody. A malformed principal is a `400`. See [Restricting an item](#9-restricting-an-item). | | `user_name` | The speaker for the item: the author of a text item, or the person in a conversation's `user` turns. Default `"User"`. | @@ -254,7 +254,7 @@ Use `instructions` to steer extraction. Set it on the request to apply it to eve } ``` -`attributes` are the fields you declared in the database's `database_metadata_schema`, and you can filter on them at query time with `attributes` on [`POST /query`](/essentials/v2/query#2-request). `custom_attributes` are free-form: they are stored with the item and cannot be filtered. Neither is returned on query chunks; read them from the item's row in [`POST /context/list`](/api-reference/v2/endpoint/list-documents). See [Attributes](/essentials/v2/attributes). +`attributes` are the fields you declared in the database's `database_metadata_schema`, and you can filter on them at query time with `attributes` on [`POST /query`](/essentials/v2/query#2-request). `custom_attributes` are free-form: they are stored with the item and cannot be filtered with `attributes`. Neither is returned on query chunks; read them from the item's row in [`POST /context/list`](/api-reference/v2/endpoint/list-documents). See [Attributes](/essentials/v2/attributes). --- @@ -327,7 +327,7 @@ Every key in `graph_payload` must match the `context_id` of an item in the same - `context_id` is yours. Reuse it to replace an item. - `upsert: true` (the default) **replaces**: re-ingesting a `context_id` deletes everything derived from the previous version (its chunks and its graph contribution) before writing the new one. It does not merge. - `upsert` is per item, with the request value as the default, so one call can replace some items and append others. -- When you omit `context_id`, the id is generated from `title`. Give repeated text either a `context_id` or a distinct `title`, or the second item replaces the first. +- When you omit `context_id`, the id is generated from the item's text and `title`. Give repeated text either a `context_id` or a distinct `title`, or the second item replaces the first. - `context_id` must not contain commas. --- @@ -407,7 +407,7 @@ An unrecognised field is a `400` that names it and lists the accepted fields; it Only `user`, `assistant` and `system` are accepted. Map roles like `tool` or `human` before sending. -`custom_attributes` are stored but cannot be filtered. Declare the field in `database_metadata_schema` and send it in `attributes` instead. +`attributes` cannot filter on `custom_attributes`. Declare the field in `database_metadata_schema` and send it in `attributes` instead. Every key must equal the `context_id` of an item in the same request. Anything else is a `400`. diff --git a/essentials/v2/query.mdx b/essentials/v2/query.mdx index aed2dc1a..77ff2348 100644 --- a/essentials/v2/query.mdx +++ b/essentials/v2/query.mdx @@ -94,7 +94,7 @@ The response `data` is exactly these four keys, inside the usual envelope: "relation": { "predicate": "managed by", "context": "Refund processing is managed by the Finance Department.", - "timestamp": 1782984600.0, + "timestamp": 1782984600, "relationship_id": "rel_managed_by", "chunk_id": "ck_policy_3" }, @@ -104,7 +104,7 @@ The response `data` is exactly these four keys, inside the usual envelope: } } ], - "path_summary": "Refund processing is managed by the Finance Department." + "path_summary": "Refund Processing managed by Finance Department." }, { "origin": "chunk_relation", @@ -143,7 +143,7 @@ The response `data` is exactly these four keys, inside the usual envelope: } } ], - "llm_prompt": "# Query results\n\n**Query:** How are refunds processed, and how should I answer this user?\n**Found:** 2 results across 2 sources · 2 related facts · 1 temporal fact · 1 forceful relation\nCite a result by its number in brackets, e.g. [1].\n\n## Results\n\n### 1. Refund policy\n- **Relevance:** 0.91 · **Collection:** support · **Type:** file\n- **Id:** refund-policy · **Last updated:** 2026-07-02\n\nRefunds are processed within 30 days of purchase by the Finance Department.\n\n**Enrichment:** Refund window is 30 days; Finance owns refund processing.\n\n---\n\n### 2. Support chat with Priya\n- **Relevance:** 0.84 · **Collection:** support · **Type:** message\n- **Id:** chat-2026-07-29 · **Last updated:** 2026-07-29\n\nuser: Keep refund answers short please\nassistant: Got it.\n\n**Enrichment:** User prefers short answers about refunds.\n\n## Forceful relations\n\nLinked to a result by the author at ingest time (forceful_relations), not by relevance to this query.\n\n### R1. Refund FAQ\n- **Linked from:** refund-policy · **Collection:** support\n- **Id:** refund-faq\n\nFAQ: refunds to a card take 5 to 7 business days to appear.\n\n## Related facts\n\n- [P1] **Refund Processing** -managed by→ **Finance Department** (relevance 0.81) [1]\n Refund processing is managed by the Finance Department.\n- [P2] **User** -prefers→ **short answers** (relevance 0.74) [2]\n The user prefers short answers about refunds.\n\n## Temporal facts\n\n- **Refund policy** *effective_from* → **June 2026** (from 2026-06-01, precision: month, status: ongoing; evidence: \"from June\") [1]\n\n## Sources\n\n1. **Refund policy** (file, id: refund-policy) · https://docs.acme.com/refunds · updated 2026-07-02\n2. **Support chat with Priya** (message, id: chat-2026-07-29) · updated 2026-07-29\n3. **Refund FAQ** (id: refund-faq)" + "llm_prompt": "# Query results\n\n**Query:** How are refunds processed, and how should I answer this user?\n**Found:** 2 results across 2 sources · 2 related facts · 1 temporal fact · 1 forceful relation\nCite a result by its number in brackets, e.g. [1].\n\n## Results\n\n### 1. Refund policy\n- **Relevance:** 0.91 · **Collection:** support · **Type:** file\n- **Id:** refund-policy · **Last updated:** 2026-07-02\n\nRefunds are processed within 30 days of purchase by the Finance Department.\n\n**Enrichment:** Refund window is 30 days; Finance owns refund processing.\n\n---\n\n### 2. Support chat with Priya\n- **Relevance:** 0.84 · **Collection:** support · **Type:** message\n- **Id:** chat-2026-07-29 · **Last updated:** 2026-07-29\n\nuser: Keep refund answers short please\nassistant: Got it.\n\n**Enrichment:** User prefers short answers about refunds.\n\n## Forceful relations\n\nLinked to a result by the author at ingest time (forceful_relations), not by relevance to this query.\n\n### R1. Refund FAQ\n- **Linked from:** refund-policy · **Collection:** support\n- **Id:** refund-faq\n\nFAQ: refunds to a card take 5 to 7 business days to appear.\n\n## Related facts\n\n- [P1] **Refund Processing** -managed by→ **Finance Department** [1]\n- [P2] **User** -prefers→ **short answers** (relevance 0.74) [2]\n The user prefers short answers about refunds.\n\n## Temporal facts\n\n- **Refund policy** *effective_from* → **June 2026** (from 2026-06-01, precision: month, status: ongoing; evidence: \"from June\") [1]\n\n## Sources\n\n1. **Refund policy** (file, id: refund-policy) · https://docs.acme.com/refunds · updated 2026-07-02\n2. **Support chat with Priya** (message, id: chat-2026-07-29) · updated 2026-07-29\n3. **Refund FAQ** (id: refund-faq)" }, "error": null, "meta": { @@ -162,7 +162,7 @@ Most integrations only need `llm_prompt`: put it in the model call and you are d ## 2. Request -[Follow this for when to use `database` and `collection`](./databases-and-collections#2-when-to-use-each). `database` was formerly `tenant_id` and `collection` was formerly `sub_tenant_id`; the old names remain accepted as deprecated aliases. +See [When to use each](/essentials/v2/databases-and-collections#2-when-to-use-each) for choosing `database` and `collection`. `database` was formerly `tenant_id` and `collection` was formerly `sub_tenant_id`; the old names remain accepted as deprecated aliases. ### Scope @@ -198,10 +198,10 @@ Most integrations only need `llm_prompt`: put it in the model call and you are d | `query_by` | `"hybrid"` or `"text"` | `hybrid` (default) blends semantic and BM25; `text` is BM25 only, paired with `operator`. | | `operator` | `"or"`, `"and"`, `"phrase"` | BM25 term matching for `query_by: "text"`. Default `"or"`. | | `mode` | `"auto"`, `"fast"`, `"thinking"` | `auto` (default) scores the query and routes to `fast` or `thinking`. `thinking` expands the query, reranks and follows declared relations; `fast` is one pass. | -| `alpha` | float `0.0` to `1.0`, or `"auto"` | Semantic versus keyword blend for `hybrid`. Default `0.8`. | -| `max_results` | integer | Maximum chunks to return. Default `10`, maximum `50`. | -| `recency_bias` | float `0.0` to `1.0` | Boost for newer content. Send `0` to disable recency entirely. | -| `query_apps` | boolean | Adds the app-aware lane (exact IDs, actors, thread and parent traversal) for connector content, on top of normal retrieval. Set `false` to skip it. | +| `alpha` | float `0.0` to `1.0`, or `"auto"` | Semantic versus keyword blend for `hybrid`. Default `0.8`; `"auto"` also resolves to `0.8`. | +| `max_results` | integer | Maximum chunks to return. Default `10`, maximum `250`. | +| `recency_bias` | float `0.0` to `1.0` | Boost for newer content. Default `0.4`; send `0` to disable recency entirely. | +| `query_apps` | boolean | Default `true`. Adds app-aware retrieval (exact IDs, actors, thread and parent traversal) for connector content, on top of normal retrieval. Set `false` to skip it. | ### Graph and relations @@ -246,18 +246,18 @@ The matched pieces of your items, ranked. Preserve the order. ### `graph[]` -One flat array of paths through the [context graph](/essentials/v2/context-graphs): paths grown from the query first, then paths expanded from the returned chunks. The array is deduplicated across both lanes (a path both lanes found is reported once, as a `query_path`) and is not capped: every path that survives deduplication is returned. `[]` when `graph_context` is `false` or nothing connects. +One flat array of paths through the [context graph](/essentials/v2/context-graphs): paths grown from the query first, then paths expanded from the returned chunks. The array is deduplicated across both origins (a path found both ways is reported once, as a `query_path`) and is not capped: every path that survives deduplication is returned. `[]` when `graph_context` is `false` or nothing connects. | Field | Type | Meaning | | --- | --- | --- | -| `origin` | string | Which lane found the path. `"query_path"`: grown from the entities in the query. `"chunk_relation"`: the neighbourhood of a returned chunk. | +| `origin` | string | How the path was found. `"query_path"`: grown from the entities in the query. `"chunk_relation"`: the neighbourhood of a returned chunk. | | `triplets[]` | array | The chain of `source`, `relation`, `target` steps that make up the path. | | `triplets[].source` | object | `{ entity_id, name }`. | | `triplets[].target` | object | `{ entity_id, name }`. | | `triplets[].relation.predicate` | string | The relation, for example `subscribed to`. | | `triplets[].relation.context` | string | The sentence the relation was extracted from. | | `triplets[].relation.temporal_details` | string | When the relation holds, for example `since June`. Omitted when empty. | -| `triplets[].relation.timestamp` | number | The relation's timestamp in Unix epoch seconds, as a float (for example `1782984600.0`). Omitted when the edge has none. | +| `triplets[].relation.timestamp` | number | When the relation was introduced (the date of the source it was extracted from), in Unix epoch seconds, possibly fractional (for example `1782984600`). Omitted when the edge has none. | | `triplets[].relation.relationship_id` | string | The relation's id. | | `triplets[].relation.chunk_id` | string | The chunk this relation was extracted from. Use it to attach the hop to a chunk, below. | | `path_summary` | string | One sentence summarizing the whole path. Never empty: when the server wrote no summary for a path, it narrates the hops, such as `Priya owns refund processing.` | @@ -341,7 +341,7 @@ The sections, in order: | `# Query results` | `**Query:**` (the query); an `**Interpreted:**` line when the query was widened by an alias (a workspace nickname for a name) or a resolved reference; a `**Found:**` line counting what follows; a `**Note:**` line when a temporal, source or profile lookup was degraded or truncated, so a thin answer is not read as an absence; and, when there is a result, the line telling the model to cite it by its number. | | `## Results` | One block per entry of `chunks[]`, in ranked order, separated by `---`: a `### 1. title` heading; a line with `**Relevance:**` (the `score`), `**Collection:**`, `**Type:**` and `**Category:**` (the `enrichment_kind`); a line with `**Id:**` (the `context_id`) and `**Last updated:**`; the chunk's `content`; then `**Enrichment:**` with the `enrichment`. | | `## Forceful relations` | A guide line, then one `### R1. title` block per entry of `forceful_relations[]`, laid out like a result, with `**Linked from:**` (the `via.from` context, when it is not `""`) in place of `**Relevance:**`. | -| `## Related facts` | One line per path in `graph[]`, such as `- [P1] **A** -pred→ **B** (relevance 0.81) [1]`: the path's label, its chain of hops, the path's relevance after reranking in parentheses (printed only here: `graph[]` carries no score), and the results its hops were extracted from. A path with no reranked score, such as a graph summary a `thinking` query builds, has no parenthetical at all: `- [P3] **A** -pred→ **B** [1]`. The line never says which lane found the path. The `path_summary` is indented on the line under it, unless it only narrates the hops the chain already shows. | +| `## Related facts` | One line per path in `graph[]`, such as `- [P1] **A** -pred→ **B** (relevance 0.81) [1]`: the path's label, its chain of hops, the path's relevance after reranking in parentheses (printed only here: `graph[]` carries no score), and the results its hops were extracted from. A path with no reranked score, such as a graph summary a `thinking` query builds, has no parenthetical at all: `- [P3] **A** -pred→ **B** [1]`. The line never says how the path was found (`graph[].origin` does). The `path_summary` is indented on the line under it, unless it only narrates the hops the chain already shows. | | `## Temporal facts` | For a "how long between" question, a `**Duration:**` line first: the computed days, whether they are approximate, and the two dated facts it was measured between. Then one line per dated fact the query engaged (the facts behind `chunks[].temporal`): subject, relation and object, then the resolved window, fact type, precision and status, with the evidence phrase set apart after a `;`, citing its result (or naming its source id when that fact's chunk is not a result). | | `## Source facts` | App-native facts about the sources behind the results (who acted, in what role, where, in which thread, from which connector, when synced), citing their result. Prompt only: no JSON key carries them. | | `## Profiles` | The entity profiles the query selected, one `### name` block each: headline, summary and the profile's statements. Prompt only. | @@ -407,8 +407,7 @@ FAQ: refunds to a card take 5 to 7 business days to appear. ## Related facts -- [P1] **Refund Processing** -managed by→ **Finance Department** (relevance 0.81) [1] - Refund processing is managed by the Finance Department. +- [P1] **Refund Processing** -managed by→ **Finance Department** [1] - [P2] **User** -prefers→ **short answers** (relevance 0.74) [2] The user prefers short answers about refunds. @@ -444,8 +443,8 @@ Surface it to your agent verbatim, and let the model cite the labels. When you n Most of the time the defaults are right. When they are not, here is where to start: - `collections`: put the person's collection above the shared one (`{ "user_alex": 2, "company": 1 }`) for personalized answers. The weights rank, they do not exclude. -- `mode`: `auto` routes each query; pick `fast` or `thinking` explicitly when you know your traffic shape and want a deterministic pipeline. `auto` also sets `graph_context` to match the pipeline it picks. -- `alpha`: start at `0.8`. Lower toward `0.3` to `0.5` when the query contains literal tokens (error codes, SKUs, product names). Raise toward `0.9` for conceptual questions. Use `"auto"` when query shape varies across calls. +- `mode`: `auto` routes each query; pick `fast` or `thinking` explicitly when you know your traffic shape and want a deterministic pipeline. +- `alpha`: start at `0.8`. Lower toward `0.3` to `0.5` when the query contains literal tokens (error codes, SKUs, product names). Raise toward `0.9` for conceptual questions. - `max_results`: start at `10`. Drop to `5` for tight context windows; raise to `20` if you rerank downstream. - `graph_context`: keep it on when answers benefit from entity relationships (multi-hop questions, "how does X relate to Y"). Pair with `mode: "thinking"`; in `fast` mode the graph slice is shallow. - `query_apps`: keep it on when querying connector content (Slack, Gmail, Confluence, Jira, Salesforce) so exact IDs, actors and threads resolve. @@ -462,13 +461,12 @@ Most of the time the defaults are right. When they are not, here is where to sta | Symptom | Cause | Fix | | --- | --- | --- | -| `graph` is `[]` | `graph_context: false`, or `mode` resolved to `fast`, or nothing connects the results | Set `graph_context: true` with `mode: "thinking"`. An empty array is normal when there is nothing to return. | +| `graph` is `[]` | `graph_context: false`, or nothing connects the results | Leave `graph_context` on; `mode: "thinking"` explores more of the graph. An empty array is normal when there is nothing to return. | | `forceful_relations` is `[]` | Nothing in the hits declared `forceful_relations`, `follow_forceful_relations: false`, or the query ran in `fast` mode | Declare relations at ingest, leave the flag on, and use `mode: "thinking"`. | | Recent items do not appear | Indexing not finished | Poll `GET /context/status?ids=...&database=...`; chunks are invisible until processing reaches at least `graph_creation`. | -| `attributes` does not narrow results | The key is not declared in `database_metadata_schema`, or the value does not match | Declare the field and send it in `attributes` at ingest; filter with an operator such as `$eq`. `custom_attributes` are never filterable. | +| `attributes` does not narrow results | The key is not declared in `database_metadata_schema`, or the value does not match | Declare the field and send it in `attributes` at ingest; filter with an operator such as `$eq`. `attributes` cannot filter on `custom_attributes`. | | Chunk has no title or url | Chunks carry no source details by design | Read them from `llm_prompt`, or call `POST /context/list` with `ids: [""]`. | | `operator: "phrase"` ignored | `query_by` is not `"text"` | `operator` only applies to BM25 text query. | -| `graph_context` value ignored | `mode` is `auto` (or omitted) | `auto` overrides `graph_context` to match the pipeline it picks. Set `mode` explicitly. | --- diff --git a/essentials/v2/semantic-search.mdx b/essentials/v2/semantic-search.mdx index 1557d264..a2430483 100644 --- a/essentials/v2/semantic-search.mdx +++ b/essentials/v2/semantic-search.mdx @@ -1,9 +1,9 @@ --- title: "Semantic Search & Retrieval" -description: "How semantic, keyword bm25, graph, and metadata signals work together in HydraDB query." +description: "How semantic, keyword (BM25), graph, and attribute signals work together in HydraDB query." --- -Semantic search is useful because it retrieves by meaning instead of exact wording. It is also incomplete on its own: production agents need exact matches, freshness, scope, database isolation, metadata filters, and graph relationships. HydraDB query combines those signals so you can retrieve context that is useful, not just similar. +Semantic search is useful because it retrieves by meaning instead of exact wording. It is also incomplete on its own: production agents need exact matches, freshness, scope, database isolation, attribute filters, and graph relationships. HydraDB query combines those signals so you can retrieve context that is useful, not just similar. --- @@ -28,7 +28,7 @@ Pure vector search can miss important production constraints: - Exact identifiers such as `E_AUTH_429` or `payments-worker-v4` may be generalized away. - A project name can collide with a normal word, like `strawberry` the project vs strawberry the fruit. -- Old and new documents can look equally relevant without recency or metadata signals. +- Old and new documents can look equally relevant without recency or attribute signals. - Different users can need different context for the same query. - Relationship questions need graph context, not only similar text chunks. @@ -38,7 +38,7 @@ That is why HydraDB exposes semantic retrieval through `query_by: "hybrid"` insi ## The `alpha` Parameter -`alpha` controls the semantic-vs-keyword-bm25 blend when `query_by: "hybrid"`. Higher values lean semantic. Lower values lean keyword bm25. +`alpha` controls the semantic versus keyword (BM25) blend when `query_by: "hybrid"`. Higher values lean semantic. Lower values lean keyword. | `alpha` | Behavior | Use When | |---|---|---| @@ -104,7 +104,7 @@ result = client.query(
-`attributes` are exact constraints applied during retrieval. Use them whenever the query has a scope that should not be violated. Keys are the fields declared in `database_metadata_schema` and sent as `attributes` at ingest; `custom_attributes` are never filterable. +`attributes` are exact constraints applied during retrieval. Use them whenever the query has a scope that should not be violated. Keys are the fields declared in `database_metadata_schema` and sent as `attributes` at ingest; `custom_attributes` cannot be filtered with `attributes`. --- @@ -298,13 +298,13 @@ See [How to Use API Results](/essentials/v2/api-results) for complete examples. ## Mental Model -Semantic search finds text that means the same thing. Keyword BM25 search finds text that says the same thing. Graph context finds connected entities. Attribute filters decide what is allowed to be queried. `POST /query` combines all four behind one endpoint via `collections`, `query_by`, `attributes`, and `graph_context` so your agents get context that is scoped, relevant, and explainable. +Semantic search finds text that means the same thing. Keyword BM25 search finds text that says the same thing. Graph context finds connected entities. Attribute filters decide what is allowed to be queried. `POST /query` combines all four behind one endpoint via `collections`, `query_by`, `attributes`, and `graph_context`. --- ## Related -- [Query](/essentials/v2/query) - full parameter reference and parallel query patterns -- [Context Graphs](/essentials/v2/context-graphs) - how graph context enriches retrieval -- [Attributes](/essentials/v2/attributes) - designing filterable schemas -- [How to Use API Results](/essentials/v2/api-results) - turning the response into an LLM prompt +- [Query](/essentials/v2/query): full parameter reference and parallel query patterns +- [Context Graphs](/essentials/v2/context-graphs): how graph context enriches retrieval +- [Attributes](/essentials/v2/attributes): designing filterable schemas +- [How to Use API Results](/essentials/v2/api-results): turning the response into an LLM prompt diff --git a/essentials/v2/webhooks.mdx b/essentials/v2/webhooks.mdx index 53e336dd..0a1fa90f 100644 --- a/essentials/v2/webhooks.mdx +++ b/essentials/v2/webhooks.mdx @@ -66,7 +66,7 @@ Your webhook URL must be reachable from the public internet. Localhost and priva -Webhook management endpoints return their response object directly, not inside the standard v2 `{ success, data, error, meta }` envelope used by `/databases`, `/context/*`, and `/query`. +Webhook management endpoints return the standard v2 `{ success, data, error, meta }` envelope. The responses on this page show the `data` object. ### Register with cURL @@ -314,13 +314,13 @@ For failed indexing, the payload can include `error_code` and `error_message`: | `event` | Event type. Currently `indexing.status_changed`. | | `delivery_id` | Stable ID for this event. Store it to deduplicate retries. | | `id` | The item's `context_id`: the one you supplied at ingestion, or the generated one. For connector-synced content, the connector item's id. | -| `database` | The name of the database you ingested into - the value you sent as `database` (or `tenant_id`) on the ingest request. Empty only for items ingested before this field existed. | +| `database` | The name of the database you ingested into: the value you sent as `database` (or `tenant_id`) on the ingest request. Empty only for items ingested before this field existed. | | `collection` | Collection scope for the indexed item. | | `status` | Terminal indexing status. Usually `completed` or `errored`. | | `timestamp` | Time the webhook payload was created. | | `error_code` | Present when available for failed processing. | | `error_message` | Present when available for failed processing. | -| `tenant_id` | Deprecated. An identifier for the database - not the name you ingested into. Always present. | +| `tenant_id` | Deprecated. An identifier for the database, not the name you ingested into. Always present. | | `sub_tenant_id` | Deprecated alias for `collection`, carrying the same value. | @@ -330,7 +330,7 @@ Older examples may refer to this identifier as `doc_id`. New webhook payloads us **`tenant_id` and `database` do not carry the same value.** `database` is the name you ingested into (`marketing-docs`); `tenant_id` is an identifier for it -(`kv3qz7mabx`). Route and filter on **`database`** - it is the only field that +(`kv3qz7mabx`). Route and filter on **`database`**: it is the only field that matches what you sent. `tenant_id` still carries the same identifier it always has, so integrations @@ -351,7 +351,7 @@ The dashboard **Send Test** button sends a synthetic event. It does not create a Nothing was ingested, so there is no database name to report: the test payload sets -every scope field - including `database` - to your organisation ID. A real delivery +every scope field, including `database`, to your organisation ID. A real delivery reports the database you ingested into. Match on `test: true` (or the `test_` prefix on `delivery_id`) to tell the two apart. @@ -454,7 +454,7 @@ Fail closed. If the signing secret is missing from your environment, reject the Your endpoint should return a `2xx` response quickly. Do any slow work after you acknowledge the request. -These wire the verifier above into a real handler. Note that both read the **raw** body before parsing. +These wire the verifier above into a real handler. Both read the **raw** body before parsing. @@ -603,6 +603,7 @@ curl 'https://api.hydradb.com/webhooks/indexing/deliveries?limit=20' \ "status": "delivered", "indexing_status": "completed", "event_type": "indexing.status_changed", + "webhook_url": "https://api.example.com/webhooks/hydradb", "attempts": 1, "error_code": null, "error_message": null, @@ -615,7 +616,7 @@ curl 'https://api.hydradb.com/webhooks/indexing/deliveries?limit=20' \ } ``` -Delivery history uses `doc_id` internally. The outbound webhook payload uses `id`. +Delivery history calls the item's id `doc_id`. The outbound webhook payload calls it `id`. ### Filter deliveries diff --git a/get-started/v2/core-concepts.mdx b/get-started/v2/core-concepts.mdx index 332d5f83..3852113c 100644 --- a/get-started/v2/core-concepts.mdx +++ b/get-started/v2/core-concepts.mdx @@ -3,8 +3,6 @@ title: "Core Concepts" description: "A tour of the primitives that make HydraDB: databases and collections, items, query, attributes, the context graph and access control." --- -> A short overview of each primitive, with links to the page that covers it in depth. - | Primitive | What it is | Deep dive | | --- | --- | --- | | **Databases and collections** | Isolated databases, partitioned into collections per user, team or project | [Databases and collections](/essentials/v2/databases-and-collections) | @@ -102,7 +100,7 @@ Read more: [Databases and collections](/essentials/v2/databases-and-collections) Attributes make retrieval deterministic. Production systems often need hard filters: "only Engineering docs", "only approved policies". - `attributes`: fields you declare in the database's `database_metadata_schema` and filter on at query time. -- `custom_attributes`: free-form fields attached to an item. Returned with results, not filterable. +- `custom_attributes`: free-form fields stored with an item. Not filterable with `attributes`. At ingest: diff --git a/get-started/v2/introduction.mdx b/get-started/v2/introduction.mdx index 4aaa3beb..555f5d3d 100644 --- a/get-started/v2/introduction.mdx +++ b/get-started/v2/introduction.mdx @@ -17,7 +17,7 @@ You ingest it as items into one database and ask one query. HydraDB builds a con > _VectorDBs find what's similar. But your agents want what's useful._ -Vector search can be reasoning-blind and meaning-blind. It finds the closest matching embeddings to your query and stops there. It can't tell "Python" the programming language from "Python" the snake and has no answer for "who owns this customer escalation." or "how has this projected evolved over the last 3 years?" It also serves identical results to everyone. Your AE querying "project Acme" needs the latest sales deck and competitive notes. Your engineer running the same query needs the changelog and architecture decisions. A simple query returns the same list to both. It fails to take into account what each of them prefers. +Vector search can be reasoning-blind and meaning-blind. It finds the closest matching embeddings to your query and stops there. It can't tell "Python" the programming language from "Python" the snake and has no answer for "who owns this customer escalation?" or "how has this project evolved over the last 3 years?" It also serves identical results to everyone. Your AE querying "project Acme" needs the latest sales deck and competitive notes. Your engineer running the same query needs the changelog and architecture decisions. A simple query returns the same list to both. It fails to take into account what each of them prefers. HydraDB answers those with the graph and with collections: each person's preferences live in their own collection, shared knowledge and decisions live in shared ones, and one query weighs them together. @@ -33,17 +33,17 @@ HydraDB is designed for teams building scalable, stateful AI agents, whether you ## The principle -We give you primitives so that you can build your own context stores, memory layers, and workflows that require context for your AI. Think of us a graph-native context delivery mechanism for your agents. +We give you primitives so that you can build your own context stores, memory layers, and workflows that require context for your AI. Think of us as a graph-native context delivery mechanism for your agents. -The graph, the context primitives, the retrieval pipeline, and the ranking knobs are yours to compose. Your context. Your opinions. +The graph, the context primitives, the retrieval pipeline, and the ranking knobs are yours to compose. Your context. Your opinions. --- ## Performance -- **Long-Context Accuracy:** Achieves a 90%\+ on LongMemEvals +- **Long-Context Accuracy:** Scores 90%\+ on LongMemEval - **Low Latency:** Delivers sub-200ms retrieval latency -- **Strict database isolation.** No cross-database aggregation, ever. Meaning your RBACs are safe and respected at all times. +- **Strict database isolation:** No query reads across databases. View the full technical breakdown in our [benchmarks](https://benchmarks.hydradb.com/). diff --git a/get-started/v2/quickstart.mdx b/get-started/v2/quickstart.mdx index 67d29fee..2aa97fed 100644 --- a/get-started/v2/quickstart.mdx +++ b/get-started/v2/quickstart.mdx @@ -208,8 +208,9 @@ curl -s -X POST "$API/context/ingest" "${AUTH[@]}" \ \"collection\": \"user_alex\", \"context\": [{ \"context_id\": \"chat-alex-001\", + \"user_name\": \"alex\", \"conversation\": [ - { \"role\": \"user\", \"content\": \"Keep answers short, I read on my phone.\", \"name\": \"alex\" }, + { \"role\": \"user\", \"content\": \"Keep answers short, I read on my phone.\" }, { \"role\": \"assistant\", \"content\": \"Got it, short answers.\" } ], \"happened_at\": \"2026-09-01\" @@ -242,7 +243,7 @@ curl -s -X POST "$API/query" "${AUTH[@]}" \ ``` -The response has four keys. `chunks` are the pieces of your items that matched, ranked, each with a `score` and its `context_id`. `graph` is the list of relation paths HydraDB found between them, such as Alex, their preference for short answers, and the refund they asked about, each with a one-sentence `path_summary`. `forceful_relations` holds items you linked at ingest (none here). `llm_prompt` is all of that as one string with citation labels, ready to drop into your model call: +The response has four keys. `chunks` are the pieces of your items that matched, ranked, each with a `score` and its `context_id`. `graph` is the list of relation paths HydraDB found between them, such as Alex and their preference for short answers, each with a one-sentence `path_summary`. `forceful_relations` holds items you linked at ingest (none here). `llm_prompt` is all of that as one string with citation labels, ready to drop into your model call: ```python messages = [{"role": "system", "content": results.data.llm_prompt}, @@ -269,7 +270,7 @@ flowchart LR style E fill:#0f172a,stroke:#334155,stroke-width:2px,color:#f8fafc,stroke-linecap:round ``` -Steps 1 and 3 are **asynchronous**: HydraDB provisions infrastructure and indexes your content in the background, so each needs a short polling loop. Steps 2, 4 and 5 run in real time. The same loop scales as your data grows; nothing in the code changes between 10 items and 10,000. +Creating a database and indexing are **asynchronous**: HydraDB provisions infrastructure and indexes your content in the background, so each is followed by a short polling loop. The ingest call returns `202` as soon as the items are queued, and querying runs in real time. One ingest request takes up to 100 items; send more requests for more. --- diff --git a/plugins/claude-code.mdx b/plugins/claude-code.mdx index eea0752e..4aab1e4c 100644 --- a/plugins/claude-code.mdx +++ b/plugins/claude-code.mdx @@ -1,6 +1,6 @@ --- title: "Claude Code" -description: "HydraDB plugin for Claude Code. Persistent memory and contextual awareness across sessions and projects." +description: "HydraDB plugin for Claude Code. Recalls context before each prompt and saves conversations and workspace docs across sessions and projects." --- ## Quick Start @@ -127,7 +127,7 @@ The earlier names `/hydradb:status`, `/hydradb:search`, `/hydradb:remember`, `/h ## Modes -### `captureMode` - how conversations are saved +### `captureMode`: how conversations are saved | Value | Behavior | | ---------------- | ------------------------------------------------------------------------------------------- | @@ -136,15 +136,15 @@ The earlier names `/hydradb:status`, `/hydradb:search`, `/hydradb:remember`, `/h | `both` | Saves individual turns and a rolling session transcript | | `off` | No automatic saves; manual saves still work via `/hydradb:ingest --session` | -### `recallMode` - speed vs. depth +### `recallMode`: speed vs. depth | Value | Behavior | | ---------- | ---------------------------------------------- | -| `fast` | **(default)** Lower latency, standard recall | -| `thinking` | Deeper reasoning-based recall via graph traversal; also follows forceful relations | +| `fast` | **(default)** One pass, lower latency | +| `thinking` | Expands the query, reranks and follows forceful relations; slower | - By default, HydraDB syncs each conversation pair (user and assistant) that does not include the `ignoreMarker` (`hydra-ignore`). For manual-only capture, set `captureMode` to `off` and use `/hydradb:ingest --session` or `/hydradb:ingest --note `. + By default, the plugin saves each exchange (your prompt and the reply) that does not include the `ignoreMarker` (`hydra-ignore`). For manual-only capture, set `captureMode` to `off` and use `/hydradb:ingest --session` or `/hydradb:ingest --note `. --- @@ -178,7 +178,7 @@ The earlier names `/hydradb:status`, `/hydradb:search`, `/hydradb:remember`, `/h | `maxKnowledgeResults` | `4` | Added to `maxMemoryResults`, as above | | `maxFileSizeBytes` | `52428800` (50 MB) | Max file size for workspace sync | | `maxFilesPerSync` | `25` | Max files synced per pass | -| `maxMemoryCharsPerChunk` | `52428800` (50 MB) | Max characters per synced item; a longer file is split into numbered parts, each its own item | +| `maxMemoryCharsPerChunk` | `52428800` | Max characters per synced item; a longer file is split into numbered parts, each its own item | ### Timeouts @@ -196,19 +196,18 @@ The earlier names `/hydradb:status`, `/hydradb:search`, `/hydradb:remember`, `/h | `ignoreMarker` | `hydra-ignore` | Add to a file or prompt to skip capture and sync | - By default, HydraDB includes `.md` and `.mdx` files in workspace sync. To include additional file types, extend `includeGlobs` in your config file. + By default, workspace sync includes `.md` and `.mdx` files. To include additional file types, extend `includeGlobs` in your config file. ### Advanced | Variable | Default | Description | | ------------------------------------ | ------------------------ | --------------------------------------------------------- | -| `subTenantId` | - | Workspace-level collection within your database | | `userName` | - | Your name: the speaker name on your conversation turns, also stored with notes and session transcripts | | `apiBaseUrl` | `https://api.hydradb.com`| HydraDB API base URL | | `memoryCustomInstructions` | - | Instructions that steer enrichment of saved conversations, sessions and notes | | `workspaceMemoryCustomInstructions` | - | Instructions that steer enrichment of synced workspace docs | -| `debug` | `false` | Enable debug logging to `.hydradb-plugin-data/debug.log` | +| `debug` | `false` | Enable debug logging to `debug.log` in the plugin data directory | --- @@ -218,11 +217,10 @@ The plugin resolves config in this order (later sources override earlier ones): 1. Built-in defaults 2. `HYDRADB_PLUGIN_CONFIG` env var (absolute path to a JSON file) -3. `${CLAUDE_PLUGIN_DATA}/config.json` -4. `.hydradb-plugin-data/config.json` -5. `.hydradb-plugin.json` *(workspace-shared - safe to commit to git)* -6. `.hydradb-plugin.local.json` *(workspace-local - add to `.gitignore`)* -7. Environment variable overrides +3. `config.json` in the plugin data directory: `${CLAUDE_PLUGIN_DATA}` when Claude Code sets it, otherwise `.hydradb-plugin-data/` in the workspace +4. `.hydradb-plugin.json` *(workspace-shared, safe to commit to git)* +5. `.hydradb-plugin.local.json` *(workspace-local, add to `.gitignore`)* +6. Environment variable overrides --- @@ -231,8 +229,6 @@ The plugin resolves config in this order (later sources override earlier ones): | Use case | `captureMode` | `recallMode` | | ----------------------------- | ---------------- | ------------ | | Default / everyday use | `session-upsert` | `fast` | -| Cross-session continuity | `session-upsert` | `fast` | -| Shared team context | `session-upsert` | `fast` | | Maximum recall coverage | `both` | `thinking` | | Recall only, no auto-save | `off` | `fast` | | Isolated turn snapshots | `turn` | `fast` | @@ -293,7 +289,7 @@ export HYDRADB_COLLECTION="" ## Source & Show Support - If this HydraDB plugin makes your Claude Code workflow faster (and smarter), please star the open-source repo that powers it. + The plugin is open source. If it is useful to you, star the repo. - - Create an API key from [Hydra DB](https://app.hydradb.com/keys) + - Create an API key from the [HydraDB dashboard](https://app.hydradb.com/keys) - Create or copy your database ID from the [HydraDB dashboard](https://app.hydradb.com/databases) @@ -56,7 +56,7 @@ description: "Agent-friendly command line interface for HydraDB. Query context, ``` - Agents can skip `login` altogether - every command reads `HYDRADB_API_KEY` and + Agents can skip `login` altogether: every command reads `HYDRADB_API_KEY` and `HYDRADB_DATABASE` directly from the environment, so no credential is written to disk. It also keeps the key out of `ps` output, which shows any value passed as a command-line argument to every other user on the machine. @@ -200,7 +200,7 @@ cat notes.txt | hydradb ingest --title "Meeting notes" --database my-db ```json [ - { "role": "user", "content": "Keep answers short please", "name": "soham" }, + { "role": "user", "content": "Keep answers short please" }, { "role": "assistant", "content": "Got it." } ] ``` @@ -208,7 +208,7 @@ cat notes.txt | hydradb ingest --title "Meeting notes" --database my-db | Option | Description | |---|---| | `--text`, `-t` | Text to ingest. Use `-` to read from stdin | -| `--conversation-file` | Path to a JSON list of `{role, content, name?}` turns (roles `user`, `assistant`, `system`) | +| `--conversation-file` | Path to a JSON list of `{role, content}` turns (roles `user`, `assistant`, `system`) | | `--title` | Optional title | | `--context-id` | Caller-assigned ID for the item (generated when omitted) | | `--enrich` / `--no-enrich` | Extract facts and graph relations for the item (default on) | @@ -340,8 +340,8 @@ hydradb database delete my-db --yes ## Scripting & Automation -The CLI is designed for both interactive use and scripting. Use `--output json` to get -machine-readable output that pipes cleanly into `jq`, Python, or other tools: +Use `--output json` to get machine-readable output that pipes into `jq`, Python, or +other tools: ```bash # List items as JSON and pull out their IDs @@ -371,9 +371,7 @@ done ## Source & Show Support -If HydraDB CLI makes your workflow faster, please star the open-source repo that -powers it. It helps keep it discoverable and motivates maintainers to keep shipping -improvements. +The CLI is open source. If it is useful to you, star the repo. - Here is how you can connect: - ```bash @@ -90,19 +88,22 @@ approve it in the browser, done. No API key, no config file to edit. ### What you are approving The approval screen shows the app asking to connect, what it will be able to - do, and which **database** it will read and write. It also asks whether the - app may use your *other* databases: + do, and which **database** and **collection** it will read and write. For the + collection, pick one, choose **All collections** (the app searches every + collection in the database), or create a new one by name. If you have more + than one database, it also asks whether the app may use your *other* + databases: - - **Allowed when asked** (default) - the app starts in the database you + - **Allowed when asked** (default): the app starts in the database you picked and can switch to another one of yours when you tell it to. Choose this if you work across several databases. - - **Not allowed** - the app is confined to that database, and to the - collection shown under **Advanced** (`hydra-db-mcp` unless you change - it). Anything else is refused, including a request to delete a graph in - another collection. + - **Not allowed**: the app is confined to that database, and to the + collection you picked if you picked one. Anything else is refused, + including a request to delete a graph in another collection. - You can change your mind at any time: **Settings → Connected apps** lists - every app you have connected and **Disconnect** cuts one off immediately. + You can change your mind at any time: the **Connected apps** page + (`app.hydradb.com/connected-apps`) lists every app you have connected, and + **Disconnect** cuts one off immediately. @@ -143,8 +144,8 @@ approve it in the browser, done. No API key, no config file to edit. - Add to `.vscode/mcp.json` - note the `servers` key and the - explicit `"type": "http"`: + Add to `.vscode/mcp.json` (note the `servers` key and the + explicit `"type": "http"`): ```json { @@ -281,8 +282,8 @@ client registers itself, and opens your browser. You sign in, choose a database, and approve. Your client stores a token that it refreshes silently, and you never handle a key. -Disconnect any app from **Settings → Connected apps**. That revokes its access -immediately. +Disconnect any app from the **Connected apps** page +(`app.hydradb.com/connected-apps`). That revokes its access immediately. ### API key headers @@ -293,7 +294,7 @@ independent users. Send them as headers: | ------ | ------- | -------- | | `Authorization` | Your HydraDB API key as a `Bearer` token (`X-HydraDB-Api-Key` is also accepted), or an OAuth access token | Yes | | `X-HydraDB-Database` | Database (tenant scope). API-key requests only; ignored for an OAuth token, whose scope comes from what you approved | Yes\* | -| `X-HydraDB-Collection` | Collection (sub-tenant); defaults to `hydra-db-mcp` | No | +| `X-HydraDB-Collection` | Collection (sub-tenant). Unset, a query searches every collection in the database (up to 10; past that, only the default collection) and a write goes to the default collection | No | | `X-HydraDB-Graph-Database` | Default graph database for the Cypher tools; defaults to the request's database | No | | `X-HydraDB-Graph-Collection` | Default graph collection; defaults to `default` | No | @@ -384,7 +385,7 @@ The primary endpoint is `POST /` (with `/mcp` supported as an alias); `GET /heal | -------------------- | ------------------------------------------ | ------------------------- | | `HYDRADB_API_KEY` | Your HydraDB API key | *Required* | | `HYDRADB_DATABASE` | The database to read and write | *Required* | -| `HYDRADB_COLLECTION` | Collection to partition data within the database | `hydra-db-mcp` | +| `HYDRADB_COLLECTION` | Collection to partition data within the database. Unset, queries search every collection (up to 10) and writes go to the default collection | *(unset)* | | `HYDRADB_BASE_URL` | API base URL override | `https://api.hydradb.com` | | `HYDRADB_LOG_LEVEL` | Log level: `DEBUG`, `INFO`, `WARN`, `ERROR` | `ERROR` | @@ -426,15 +427,16 @@ Use `HYDRADB_COLLECTION` to partition data across projects within one database: Give each project its own `HYDRADB_COLLECTION` to keep memory isolated, or point - several projects at the same value to share context between them. Unset, the - server writes to and reads from `hydra-db-mcp`. + several projects at the same value to share context between them. Unset, a + query searches every collection in the database (up to 10) and a write goes + to the database's default collection. --- ## Available Tools -Tool names follow the canonical HydraDB vocabulary - one verb per action, with the +Tool names follow the canonical HydraDB vocabulary: one verb per action, with the same scope names the rest of the product uses. See the [Glossary](/essentials/v2/glossary). @@ -459,7 +461,7 @@ Sends the question to `POST /query` and returns the answer described under | --------- | ---- | -------- | ----------- | | `query` | string | Yes | What you want to know, as a question or topic | | `max_results` | number | No | Chunks to return, 1-50 (default: `10`) | -| `mode` | string | No | `thinking` (default) runs graph traversal and follows forceful relations; `fast` is plain semantic search and quicker; `auto` lets HydraDB pick | +| `mode` | string | No | `thinking` (default) expands the query, reranks and follows forceful relations; `fast` is one pass and quicker; `auto` lets HydraDB pick | | `graph_context` | boolean | No | Include related facts from the context graph (`graph[]`) in the answer (default: `true`) | | `follow_forceful_relations` | boolean | No | Also return items declared related at ingest (see `forceful_relations` on `hydradb_ingest`), listed under Forceful relations with `[R1]` labels (default: `true`). They are followed in `thinking` mode | | `operator` | string | No | `or`, `and`, or `phrase`. Switches the query to keyword retrieval, which matches the literal words instead of running hybrid semantic search. Leave unset for normal searches | @@ -474,8 +476,8 @@ Sends the question to `POST /query` and returns the answer described under #### What the query returns -The tool result is the server-built `llm_prompt`, verbatim and whole: it is never -trimmed or truncated. It is markdown: `# Query results`, then `## Results`, +The tool result is the server-built `llm_prompt`, verbatim and whole, after a +one-line count of what was found: it is never trimmed or truncated. It is markdown: `# Query results`, then `## Results`, `## Forceful relations`, `## Related facts`, `## Temporal facts`, `## Source facts`, `## Profiles`, `## Code search` and `## Sources`, each only when there is something to show. Results are numbered `1`, `2`; forceful relations (linked by the author at ingest, not matched by the query) `R1`, `R2`; @@ -660,7 +662,7 @@ item becomes searchable. ## Source & Show Support - If this HydraDB MCP server makes your agentic memory workflow faster (and smarter), please star the open-source repo that powers it. + The MCP server is open source. If it is useful to you, star the repo. ` | `/hydra-recall` | Query HydraDB and list the results with scores | | `/hydradb-list` | `/hydra-list` | List everything stored in the collection | | `/hydradb-delete ` | `/hydra-delete` | Delete one stored item by its ID | -| `/hydradb-inspect ` | `/hydra-get` | Fetch the full content of an item | +| `/hydradb-inspect ` | `/hydra-get` | Show an item's content (first 2,000 characters) | | `/hydra-onboard` | - | Show current configuration status | --- @@ -181,7 +181,7 @@ The earlier `hydra_*` names still work and print a one-time deprecation warning. | `hydradb_ingest` | `hydra_store` | Save the recent conversation (up to the last 10 turns) as a conversation item, or the given text when there is no conversation | | `hydradb_query` | `hydra_search` | Query HydraDB; returns the server-built `llm_prompt` | | `hydradb_list` | `hydra_list_memories` | List everything stored (IDs and summaries) | -| `hydradb_inspect` | `hydra_get_content` | Fetch the full content of an item by its ID (`source_id`) | +| `hydradb_inspect` | `hydra_get_content` | Fetch an item's content by its ID (`source_id`), up to the first 3,000 characters | | `hydradb_delete` | `hydra_delete_memory` | Delete one stored item by its ID (`memory_id`); use only on explicit request | --- @@ -249,14 +249,12 @@ Each section appears only when there is something to show. The `hydradb_query` t ## Source & Show Support - If this HydraDB plugin makes your OpenClaw workflow smarter, please star the open-source repos that power it. + The plugin is open source. If it is useful to you, star the repo. - Star on GitHub if you use OpenClaw too. - - + Star on GitHub if you found it useful. From 9d53d66ae7ffb5b70a9e6575ff1f68dc8d5d7d9c Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Wed, 23 Sep 2026 20:37:17 +0530 Subject: [PATCH 05/17] docs: spec from app staging a8a9f5787; received_at, 16 MiB ingest cap, Cypher Graph Collections rename (PRO-1618) - api-reference/v2/openapi.json: byte-for-byte copy of the app's application/docs/openapi.json at staging a8a9f5787 (type deprecated on every operation, tenant_id and sub_tenant_id on the unified ingest body, received_at on query chunks). - Query chunks: document received_at (when HydraDB received the item, RFC 3339, not happened_at, omitted when unrecorded) in the Query guide, the Query reference, API results, AGENTS and the CLI page. - Ingest: the 16 MiB body cap (JSON body or the context form field, 413), the tenant_id / sub_tenant_id aliases on the unified body, and a non-string tenancy key being a 400. 413 and meta.api_version on Error Responses. - Rename: the Cypher page is now "Cypher Graph Collections"; "Bring Your Own Graph" is only the ingest graph_payload page. Link text, the MCP graph tools heading and a note on the /byog path updated to match. The Cypher page is in nav, so it is no longer excluded from hygiene. - Replace spaced hyphens used as dashes on visible pages. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 16 +++-- .../v2/endpoint/configure-connector.mdx | 4 +- .../v2/endpoint/connectors-overview.mdx | 2 +- .../v2/endpoint/create-connector.mdx | 2 +- api-reference/v2/endpoint/create-tenant.mdx | 8 +-- .../v2/endpoint/delete-collection.mdx | 4 +- api-reference/v2/endpoint/delete-source.mdx | 6 +- api-reference/v2/endpoint/delete-tenant.mdx | 4 +- api-reference/v2/endpoint/ingest-context.mdx | 7 +- .../v2/endpoint/list-sub-tenants.mdx | 2 +- api-reference/v2/endpoint/query-overview.mdx | 2 +- api-reference/v2/endpoint/query.mdx | 8 ++- .../v2/endpoint/source-relations.mdx | 2 +- api-reference/v2/endpoint/source-status.mdx | 2 +- .../v2/endpoint/sources-overview.mdx | 2 +- api-reference/v2/endpoint/subgraph.mdx | 2 +- api-reference/v2/endpoint/tenant-status.mdx | 4 +- .../v2/endpoint/tenants-overview.mdx | 2 +- api-reference/v2/error-responses.mdx | 4 ++ api-reference/v2/index.mdx | 2 +- api-reference/v2/openapi.json | 66 ++++++++++++++----- essentials/v2/api-results.mdx | 3 +- essentials/v2/bring-your-own-graph.mdx | 2 +- essentials/v2/databases-and-collections.mdx | 6 +- essentials/v2/graph-collections-byog.mdx | 24 ++++--- essentials/v2/ingest.mdx | 19 +++--- essentials/v2/query.mdx | 7 +- mintlify-hygiene.toml | 3 - plugins/cli.mdx | 2 +- plugins/mcp.mdx | 4 +- 30 files changed, 138 insertions(+), 83 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index 82248b0d..1d41494e 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -657,7 +657,7 @@ One endpoint takes every item, text or conversation, into any collection of a da | `enrich` | Request-level default for every item's `enrich`. Default `true`. | | `upsert` | Request-level default for every item's `upsert`. Default `true`. | | `instructions` | Request-level default for every item's `instructions`. Default empty. | -| `graph_payload` | Optional. A graph you built yourself, keyed by `context_id`. See [Bring your own graph](#bring-your-own-graph). | +| `graph_payload` | Optional. A graph you built yourself, keyed by `context_id`. See [Bring Your Own Graph](#bring-your-own-graph). | The request-level values apply to any item that does not set the field itself, so one call can enrich some items and store others as they are, or replace some items and append others. @@ -674,7 +674,7 @@ Each item carries exactly one of `text` or `conversation`. | `enrich` | Extract entities, relations and preferences from this item. Default: the request's `enrich`, else `true`. Set `false` to store the item only as searchable text. | | `upsert` | Replace an existing item with the same `context_id`. Default: the request's `upsert`, else `true`. | | `instructions` | Steer enrichment for this item. At most 4,000 characters. Default: the request's `instructions`. | -| `happened_at` | The date the item is about, `YYYY-MM-DD` only; a timestamp is a `400`. HydraDB records when it received the item separately. | +| `happened_at` | The date the item is about, `YYYY-MM-DD` only; a timestamp is a `400`. HydraDB records when it received the item separately and returns that as `received_at` on query chunks. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. | | `custom_attributes` | Free-form fields. Not filterable. | | `forceful_relations` | `{ "context_ids": [...], "properties": {} }`: the `context_id`s this item is linked to. `properties` is an optional flat map of string, number or boolean values (at most 1 KiB) stored on each edge; `id`, `created_at`, `relation_type`, `tenant_id` and `sub_tenant_id` are reserved keys. | @@ -697,7 +697,7 @@ An unknown key is a `400` naming the key and listing the accepted ones, whether - `upsert` is per item, with the request value as the default. - Give repeated text either a `context_id` or a distinct `title`, or the second item replaces the first. -### Bring your own graph +### Bring Your Own Graph Skip extraction for an item and supply its entities and relations yourself with `graph_payload`, a map keyed by `context_id`: @@ -721,11 +721,12 @@ Skip extraction for an item and supply its entities and relations yourself with } ``` -Every key in `graph_payload` must equal the `context_id` of an item in the same request; a key that matches nothing is a `400`. A keyed item is still chunked and embedded, so it stays searchable. Entity and relation shapes and caps are on [Bring your own graph](/essentials/v2/bring-your-own-graph). +Every key in `graph_payload` must equal the `context_id` of an item in the same request; a key that matches nothing is a `400`. A keyed item is still chunked and embedded, so it stays searchable. Entity and relation shapes and caps are on [Bring Your Own Graph](/essentials/v2/bring-your-own-graph). ### Limits and validation - At most **100 items** per request, **1 MiB** of text per item, **8 MiB** of text per request. Split larger batches. +- The whole body is capped at **16 MiB** (the JSON body, or the `context` form field on the multipart form). A larger one is a `413` with `request body too large`. - `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each item. - `attributes` are capped at **16 KiB** and `custom_attributes` at **1 KiB** per item, measured on the compact JSON encoding in UTF-8 bytes (keys and punctuation count). - A validation error names the item it refers to as `context[N]`. @@ -940,6 +941,7 @@ Attribute-filtered search, on behalf of one user: "score": 0.91, "content": "Refunds are processed within 30 days of purchase by the Finance Department.", "enrichment": "Refund window is 30 days; Finance owns refund processing.", + "received_at": "2026-07-02T09:14:05Z", "temporal": [ { "content": "Refund policy effective_from June 2026. Start: 2026-06-01", "start_date": "2026-06-01", "end_date": null } ] @@ -949,7 +951,8 @@ Attribute-filtered search, on behalf of one user: "context_id": "chat-2026-07-29", "score": 0.84, "content": "user: Keep refund answers short please\nassistant: Got it.", - "enrichment": "User prefers short answers about refunds." + "enrichment": "User prefers short answers about refunds.", + "received_at": "2026-07-29T16:40:12Z" } ], "graph": [ @@ -1004,9 +1007,10 @@ Attribute-filtered search, on behalf of one user: | `content` | The chunk's own text, verbatim. Enrichment is never concatenated into it. | | `enrichment` | A plain string: what enrichment extracted from this chunk (a preference, a fact). Omitted when there is none. | | `enrichment_kind` | An optional label; omitted when none was set. | +| `received_at` | When HydraDB received the item, as an RFC 3339 timestamp. This is the ingest time, not the item's `happened_at` (which is not returned). Omitted when no receipt time is recorded, as on older items; never sent empty. | | `temporal` | Present only when the query engaged temporal reasoning: `{ content, start_date, end_date }` entries, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`. | -Chunks carry nothing about their source: no title, url, collection or attributes. `llm_prompt` prints the title, collection, last-updated date and url for the model. To read an item's stored content yourself, call `GET /context/inspect` with the chunk's `context_id`; to read its title and attributes, call `POST /context/list` with `ids: [context_id]`. +Chunks carry almost nothing about their source: no title, url, collection or attributes, only `received_at`. `llm_prompt` prints the title, collection, last-updated date and url for the model. To read an item's stored content yourself, call `GET /context/inspect` with the chunk's `context_id`; to read its title and attributes, call `POST /context/list` with `ids: [context_id]`. `graph[]`: paths through the context graph, query paths first, then paths expanded from the returned chunks. The list is deduplicated across both origins (a path found both ways is reported once, as a `query_path`) and is not capped. `[]` when `graph_context` is `false` or nothing connects. diff --git a/api-reference/v2/endpoint/configure-connector.mdx b/api-reference/v2/endpoint/configure-connector.mdx index 081357e7..5aad939d 100644 --- a/api-reference/v2/endpoint/configure-connector.mdx +++ b/api-reference/v2/endpoint/configure-connector.mdx @@ -63,7 +63,7 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/configure' \ | | Key-value pairs merged into the attributes of every synced object from this resource. Undeclared keys are accepted and stored, but only keys declared in `database_metadata_schema` are indexed for filtering. | | | Key-value pairs merged into the custom attributes of every synced object from this resource. Free-form, no schema required. | -See [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) for how these merge with system-generated fields. +See [Connectors: Overview](/api-reference/v2/endpoint/connectors-overview) for how these merge with system-generated fields. @@ -88,4 +88,4 @@ See [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) for - **Next:** [Sync Connector](/api-reference/v2/endpoint/sync-connector): trigger another sync on demand (the scheduler runs hourly by default) - **Next:** [List Connector Resources](/api-reference/v2/endpoint/connector-resources): poll `provider_cursor` to confirm sync ran - [Discover Resources](/api-reference/v2/endpoint/discover-connector-resources): find resource IDs before configuring -- [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) +- [Connectors: Overview](/api-reference/v2/endpoint/connectors-overview) diff --git a/api-reference/v2/endpoint/connectors-overview.mdx b/api-reference/v2/endpoint/connectors-overview.mdx index 165b3c11..433f4574 100644 --- a/api-reference/v2/endpoint/connectors-overview.mdx +++ b/api-reference/v2/endpoint/connectors-overview.mdx @@ -1,5 +1,5 @@ --- -title: "Connectors - Overview" +title: "Connectors: Overview" description: "Quick reference for all connector endpoints, their lifecycle, and when to call each." --- diff --git a/api-reference/v2/endpoint/create-connector.mdx b/api-reference/v2/endpoint/create-connector.mdx index c7f1c3b8..e09edb01 100644 --- a/api-reference/v2/endpoint/create-connector.mdx +++ b/api-reference/v2/endpoint/create-connector.mdx @@ -73,4 +73,4 @@ curl -X POST 'https://api.hydradb.com/connectors' \ - **Next:** [Discover Resources](/api-reference/v2/endpoint/discover-connector-resources): inspect what's available before activating - **Next:** [Configure Connector](/api-reference/v2/endpoint/configure-connector): activate resources for sync - **Teardown:** [Delete Connector](/api-reference/v2/endpoint/delete-connector) -- **Read more:** [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) +- **Read more:** [Connectors: Overview](/api-reference/v2/endpoint/connectors-overview) diff --git a/api-reference/v2/endpoint/create-tenant.mdx b/api-reference/v2/endpoint/create-tenant.mdx index 8ad5ba05..d039cf74 100644 --- a/api-reference/v2/endpoint/create-tenant.mdx +++ b/api-reference/v2/endpoint/create-tenant.mdx @@ -172,9 +172,9 @@ For parameters, data types, limits, shorthand flags, and examples, see the [Attr ## **Related Resources** -- **Next:** [Database Status](/api-reference/v2/endpoint/tenant-status) - poll until provisioning completes -- **Next:** [Ingest Context](/api-reference/v2/endpoint/ingest-context) - start ingesting data once status is ready -- **Related:** [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema) - add metadata schema fields later -- **Related:** [Delete Database](/api-reference/v2/endpoint/delete-tenant) - teardown +- **Next:** [Database Status](/api-reference/v2/endpoint/tenant-status): poll until provisioning completes +- **Next:** [Ingest Context](/api-reference/v2/endpoint/ingest-context): start ingesting data once status is ready +- **Related:** [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema): add metadata schema fields later +- **Related:** [Delete Database](/api-reference/v2/endpoint/delete-tenant): teardown - **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) - **Read more:** [Attributes](/essentials/v2/attributes) diff --git a/api-reference/v2/endpoint/delete-collection.mdx b/api-reference/v2/endpoint/delete-collection.mdx index 4c3af685..ad527c68 100644 --- a/api-reference/v2/endpoint/delete-collection.mdx +++ b/api-reference/v2/endpoint/delete-collection.mdx @@ -98,8 +98,8 @@ Common codes: `400 INVALID_INPUT` (missing `database` or `collection`), `404 DAT **Related Resources** -- **Before this:** [List Collections](/api-reference/v2/endpoint/list-sub-tenants) - find the collection ID +- **Before this:** [List Collections](/api-reference/v2/endpoint/list-sub-tenants): find the collection ID - **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source): remove specific context items without deleting the collection -- **Larger scope:** [Delete Database](/api-reference/v2/endpoint/delete-tenant) - remove the entire database +- **Larger scope:** [Delete Database](/api-reference/v2/endpoint/delete-tenant): remove the entire database - **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) diff --git a/api-reference/v2/endpoint/delete-source.mdx b/api-reference/v2/endpoint/delete-source.mdx index c6c36450..445c36fb 100644 --- a/api-reference/v2/endpoint/delete-source.mdx +++ b/api-reference/v2/endpoint/delete-source.mdx @@ -201,7 +201,7 @@ The header always wins. Without it, the server default applies. | Request | Behaviour | | --- | --- | -| No header | The server default - currently `legacy`, so `200` for every outcome. | +| No header | The server default, currently `legacy`, so `200` for every outcome. | | `X-HydraDB-Delete-Status: strict` | Honest `404` / `409` / `500`. | | `X-HydraDB-Delete-Status: legacy` | `200` for every outcome, whatever the server default. | @@ -230,6 +230,6 @@ The header always wins. Without it, the server default applies. - **Find IDs:** [List Context](/api-reference/v2/endpoint/list-documents) - **Perform a query:** [Query](/api-reference/v2/endpoint/query) - **Re-add content:** [Ingest Context](/api-reference/v2/endpoint/ingest-context) - - **Larger scope:** [Delete Database](/api-reference/v2/endpoint/delete-tenant) - removes the entire database - - **Read more:** [Context Management - Overview](/api-reference/v2/endpoint/sources-overview) + - **Larger scope:** [Delete Database](/api-reference/v2/endpoint/delete-tenant): removes the entire database + - **Read more:** [Context Management: Overview](/api-reference/v2/endpoint/sources-overview) diff --git a/api-reference/v2/endpoint/delete-tenant.mdx b/api-reference/v2/endpoint/delete-tenant.mdx index d5b877b4..c2314a24 100644 --- a/api-reference/v2/endpoint/delete-tenant.mdx +++ b/api-reference/v2/endpoint/delete-tenant.mdx @@ -89,8 +89,8 @@ Common codes: `404 DATABASE_NOT_FOUND`, `401 UNAUTHORIZED`, `400 INVALID_INPUT` **Related Resources** -- **Before this:** [List Databases](/api-reference/v2/endpoint/list-tenants) - find the database ID -- **Alternative:** [Delete Collection](/api-reference/v2/endpoint/delete-collection) - remove one collection without deleting the whole database +- **Before this:** [List Databases](/api-reference/v2/endpoint/list-tenants): find the database ID +- **Alternative:** [Delete Collection](/api-reference/v2/endpoint/delete-collection): remove one collection without deleting the whole database - **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source): remove specific context items without deleting the whole database - **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index c0dce974..b030a9d7 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -123,7 +123,7 @@ The SDK `ingest` methods take `database`, `collection`, `context`, `upsert`, `en | | Request-level default for every item's `enrich`. (default=`true`) | | | Request-level default for every item's `upsert`. (default=`true`) | | | Request-level default for every item's `instructions`. At most 4,000 characters after trimming. (default=empty) | -| | Bring your own graph: a map of `context_id` to `{ entities, relations }` that replaces graph extraction for that item. Every key must match the `context_id` of an item in the same request, otherwise `400`. See [Bring your own graph](#bring-your-own-graph) below. | +| | Bring Your Own Graph: a map of `context_id` to `{ entities, relations }` that replaces graph extraction for that item. Every key must match the `context_id` of an item in the same request, otherwise `400`. See [Bring Your Own Graph](#bring-your-own-graph) below. | ### Item fields @@ -138,7 +138,7 @@ Each item is exactly one of `text` or `conversation`. | | Extract entities, relations and preferences from this item into the graph; the output is stored separately and returned as `enrichment` on query. (default=the request's `enrich`, else `true`) | | | Replace an existing item with the same `context_id`, deleting its chunks and graph contribution first. (default=the request's `upsert`, else `true`) | | | Steer enrichment for this item. At most 4,000 characters after trimming. (default=the request's `instructions`) | -| | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately. | +| | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately and returns that as `received_at` on query chunks. | | | Declared, filterable fields; keys must be in `database_metadata_schema`. Filter with `attributes` on `/query`. See [Attributes](/essentials/v2/attributes). | | | Free-form fields. Stored with the item; not filterable and not returned on query chunks. | | | Relations you declare to other items: `{ "context_ids": ["", ...], "properties": {} }`. Followed on `/query` in `thinking` mode with `follow_forceful_relations` and returned in `forceful_relations[]`. Each id follows the same rules as `context_id`. `properties` is optional and is stored on every edge the item declares: a flat map of string, number or boolean values, at most 1 KiB as compact JSON, with no empty key and none of the reserved keys `id`, `created_at`, `relation_type`, `tenant_id` or `sub_tenant_id`. | @@ -147,6 +147,7 @@ Each item is exactly one of `text` or `conversation`. ### Limits +- The whole request body is capped at **16 MiB**: the JSON body, or the `context` form field on the multipart form. A larger one is refused with `413` (`request body too large`). - At most **100 items** per request, **1 MiB** of text per item, **8 MiB** of text per request. Text is an item's `text`, or the `content` of every turn of its `conversation`; titles and attributes are not counted. - `attributes` at most **16 KiB** and `custom_attributes` at most **1 KiB** per item, measured on their compact JSON encoding. - `title` at most **1,024 bytes**, and `instructions` at most **4,000 characters** on the request and on each item. A conversation's `system` turns are held to the same 4,000 characters when they become the item's instructions. @@ -157,7 +158,7 @@ Each item is exactly one of `text` or `conversation`. Every item is text or a conversation. To ingest a file, extract its text and send it as a `text` item. Content from connected apps arrives through [connectors](/essentials/v2/connectors). -## Bring your own graph +## Bring Your Own Graph `graph_payload` supplies the graph for an item yourself. HydraDB uses it instead of extracting a graph from that item; the item is still chunked and embedded, so it stays searchable. Each top-level key is the `context_id` of an item in the same request, so give keyed items an explicit `context_id`. See [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) for the full guide. diff --git a/api-reference/v2/endpoint/list-sub-tenants.mdx b/api-reference/v2/endpoint/list-sub-tenants.mdx index 30253576..c748de4d 100644 --- a/api-reference/v2/endpoint/list-sub-tenants.mdx +++ b/api-reference/v2/endpoint/list-sub-tenants.mdx @@ -81,7 +81,7 @@ curl -X GET 'https://api.hydradb.com/databases/collections?database=my_first_dat **Related Resources** - - **Inspect content:** [List Context](/api-reference/v2/endpoint/list-documents) - scoped to a `collection` + - **Inspect content:** [List Context](/api-reference/v2/endpoint/list-documents): scoped to a `collection` - **Delete a collection:** [Delete Collection](/api-reference/v2/endpoint/delete-collection) - **Inspect usage:** [Database Stats](/api-reference/v2/endpoint/tenant-stats) diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index 1a81b6da..f9f40378 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -1,5 +1,5 @@ --- -title: "Query - Overview" +title: "Query: Overview" description: "Quick reference for scoping, matching and retrieval modes, and what comes back." --- diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index d1d29ea8..c70ca340 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -434,6 +434,7 @@ result = client.query( "score": 0.91, "content": "Refunds are processed within 30 days of purchase by the Finance Department.", "enrichment": "Refund window is 30 days; Finance owns refund processing.", + "received_at": "2026-07-02T09:14:05Z", "temporal": [ { "content": "Refund policy effective_from June 2026. Start: 2026-06-01", @@ -447,7 +448,8 @@ result = client.query( "context_id": "chat-2026-07-29", "score": 0.84, "content": "user: Keep refund answers short please\nassistant: Got it.", - "enrichment": "User prefers short answers about refunds." + "enrichment": "User prefers short answers about refunds.", + "received_at": "2026-07-29T16:40:12Z" } ], "graph": [ @@ -546,7 +548,7 @@ result = client.query( | Key | Contents | | --- | --- | -| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content` (verbatim), `enrichment` (the extracted statement as a plain string, omitted when there is none), `enrichment_kind` (an optional label; omitted when none was set), `temporal[]` (only when the query engaged temporal reasoning; `{ content, start_date, end_date }`, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`). | +| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content` (verbatim), `enrichment` (the extracted statement as a plain string, omitted when there is none), `enrichment_kind` (an optional label; omitted when none was set), `received_at` (when HydraDB received the item, RFC 3339; this is not the item's `happened_at`, and it is omitted when no receipt time is recorded, as on older items), `temporal[]` (only when the query engaged temporal reasoning; `{ content, start_date, end_date }`, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`). | | `graph[]` | Paths through the context graph, query paths first then chunk expansions: `origin`, `triplets[]` of `source` / `relation` / `target`, plus `path_summary`. `origin` is `"query_path"` (grown from the entities in the query) or `"chunk_relation"` (the neighbourhood of a returned chunk, only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk). The array is deduplicated across both origins and is not capped. `path_summary` is never empty: when the server wrote no summary, it narrates the hops. Entities are `{ entity_id, name }`; relations are `{ predicate, context, temporal_details?, timestamp?, relationship_id, chunk_id }`, where `temporal_details` is omitted when empty and `timestamp` (Unix epoch seconds, a float) is omitted when the edge has none. `[]` when `graph_context` is `false`. | | `forceful_relations[]` | Chunks pulled in through `forceful_relations` declared at ingest, followed only in `thinking` mode: `via.from` (the context whose declaration pulled it in, may be `""`), `via.to` (the chunk's own `context_id`), `chunk` (same shape as `chunks[]`). `[]` when none, when `follow_forceful_relations` is `false`, or when the query ran in `fast` mode. | | `llm_prompt` | A server-built markdown string ready to inject into a model call: `# Query results`, then `## Results`, `## Forceful relations`, `## Related facts`, `## Temporal facts` (with a `**Duration:**` line for a "how long between" question), `## Source facts`, `## Profiles`, `## Code search` and `## Sources`, each left out when empty. Source facts, profiles, code-search answers and the duration are prompt only: no JSON key carries them. Results are cited `[1]` and forceful relations `[R1]`; related facts are labelled `[P1]`, `[P2]`, ... in `graph[]` order, as in `- [P1] **Refunds** -managed_by→ **Finance** (relevance 0.81) [1]`: the parenthetical is the path's relevance after reranking and is left out when the path has none, and the line ends with the results the path was extracted from. Sources print only web (`http` or `https`) links. `""` only when the query found nothing at all. The layout is on [Query](/essentials/v2/query#llm_prompt). | @@ -556,7 +558,7 @@ To show a chunk's graph paths under that chunk, group hops by `triplets[].relati `meta` carries `request_id`, `api_version`, `latency_ms`, `database` and `collection`, plus a `deprecation` list when the request used a deprecated name. `collection` is present when the query searched one collection (named, or the default); a `collections` fan-out omits it. -**Chunks carry no source details.** No title, url, collection, timestamps or attributes. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show an item's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. +**Chunks carry almost no source details.** A chunk has no title, url, collection or attributes; the one source detail it carries is `received_at`. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show an item's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. ## Behavior notes diff --git a/api-reference/v2/endpoint/source-relations.mdx b/api-reference/v2/endpoint/source-relations.mdx index 473b1e13..a835a889 100644 --- a/api-reference/v2/endpoint/source-relations.mdx +++ b/api-reference/v2/endpoint/source-relations.mdx @@ -240,7 +240,7 @@ while True: **Related Resources** - - **Indexing status:** [Ingestion Status](/api-reference/v2/endpoint/source-status) - confirm the graph is complete + - **Indexing status:** [Ingestion Status](/api-reference/v2/endpoint/source-status): confirm the graph is complete - **Query with graph paths:** [Query](/api-reference/v2/endpoint/query) returns graph paths in `graph[]`, controlled by the `graph_context` request flag - **Concepts:** [Context Graphs](/essentials/v2/context-graphs) diff --git a/api-reference/v2/endpoint/source-status.mdx b/api-reference/v2/endpoint/source-status.mdx index dbae4db0..78d41e25 100644 --- a/api-reference/v2/endpoint/source-status.mdx +++ b/api-reference/v2/endpoint/source-status.mdx @@ -267,7 +267,7 @@ Common codes: `400 INVALID_INPUT`, `404 DATABASE_NOT_FOUND`, `422 VALIDATION_ERR **Related Resources** - - **Before this:** [Ingest Context](/api-reference/v2/endpoint/ingest-context) - to get the IDs + - **Before this:** [Ingest Context](/api-reference/v2/endpoint/ingest-context): to get the IDs - **After completion:** [Query](/api-reference/v2/endpoint/query) - **After completion:** [Inspect Context](/api-reference/v2/endpoint/fetch-content) - **After completion:** [Context Relations](/api-reference/v2/endpoint/source-relations) diff --git a/api-reference/v2/endpoint/sources-overview.mdx b/api-reference/v2/endpoint/sources-overview.mdx index 42d98f49..43193024 100644 --- a/api-reference/v2/endpoint/sources-overview.mdx +++ b/api-reference/v2/endpoint/sources-overview.mdx @@ -1,5 +1,5 @@ --- -title: "Context Management - Overview" +title: "Context Management: Overview" description: "Quick reference for context management endpoints, their lifecycle, and when to use which." --- diff --git a/api-reference/v2/endpoint/subgraph.mdx b/api-reference/v2/endpoint/subgraph.mdx index fb8b201d..03aa1189 100644 --- a/api-reference/v2/endpoint/subgraph.mdx +++ b/api-reference/v2/endpoint/subgraph.mdx @@ -220,7 +220,7 @@ Fields a member does not have are omitted. **Related Resources** - - **Entity relations:** [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations) - the triplets extracted from text + - **Entity relations:** [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations): the triplets extracted from text - **Full content of a member:** [Inspect Context](/api-reference/v2/endpoint/fetch-content) - **Query with graph paths:** [Query](/api-reference/v2/endpoint/query) returns graph paths in `graph[]`, controlled by the `graph_context` request flag - **Concepts:** [Context Graphs](/essentials/v2/context-graphs) diff --git a/api-reference/v2/endpoint/tenant-status.mdx b/api-reference/v2/endpoint/tenant-status.mdx index 1c433cc6..4d859537 100644 --- a/api-reference/v2/endpoint/tenant-status.mdx +++ b/api-reference/v2/endpoint/tenant-status.mdx @@ -106,6 +106,6 @@ curl -X GET 'https://api.hydradb.com/databases/status?database=my_first_database **Related Resources** - - **Before this:** [Create Database](/api-reference/v2/endpoint/create-tenant) - kicks off provisioning - - **After this:** [Ingest Context](/api-reference/v2/endpoint/ingest-context) - once status is ready + - **Before this:** [Create Database](/api-reference/v2/endpoint/create-tenant): kicks off provisioning + - **After this:** [Ingest Context](/api-reference/v2/endpoint/ingest-context): once status is ready diff --git a/api-reference/v2/endpoint/tenants-overview.mdx b/api-reference/v2/endpoint/tenants-overview.mdx index 6aa5371a..d612b788 100644 --- a/api-reference/v2/endpoint/tenants-overview.mdx +++ b/api-reference/v2/endpoint/tenants-overview.mdx @@ -1,5 +1,5 @@ --- -title: "Databases - Overview" +title: "Databases: Overview" description: "Quick reference for all databases endpoints, their lifecycle, and when to call each." --- diff --git a/api-reference/v2/error-responses.mdx b/api-reference/v2/error-responses.mdx index 13144729..f20a0270 100644 --- a/api-reference/v2/error-responses.mdx +++ b/api-reference/v2/error-responses.mdx @@ -17,6 +17,7 @@ HydraDB core endpoints (`/databases`, `/context/*`, and `/query`) use the same t }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", + "api_version": "2.0.1", "latency_ms": 4.8 } } @@ -29,6 +30,7 @@ HydraDB core endpoints (`/databases`, `/context/*`, and `/query`) use the same t | `error.code` | Machine-readable code for programmatic handling. | | `error.message` | Human-readable explanation of what failed. | | `meta.request_id` | Request identifier. Include it when contacting support. | +| `meta.api_version` | The API version that served the request. | | `meta.latency_ms` | Server-side processing time in milliseconds. | @@ -44,6 +46,7 @@ Use `error.code` for branching and log `meta.request_id` for every failed reques | `403` | Authenticated, but not permitted for the resource | No | | `404` | Database, context item, or related resource was not found | No | | `409` | Conflict, usually an existing database, or a strict-mode delete of an item that is still indexing | Usually no | +| `413` | Request body too large, for example a `POST /context/ingest` body over 16 MiB. The error code is `INVALID_INPUT` | No; send a smaller request | | `422` | Well-formed request that failed validation | No | | `429` | Rate limit exceeded | Yes, with backoff | | `500` | Internal server error | Yes, with backoff | @@ -257,6 +260,7 @@ Database creation is asynchronous. After `POST /databases`, poll [`GET /database - `forceful_relations.properties` has a nested value, an empty key or a reserved key, or is over 1 KiB. - A `graph_payload` key matches no `context_id` in the same request. - An `acl` entry is not a valid principal. +- The body is over 16 MiB. That is a `413` rather than a `400`, with the message `request body too large`. - The request exceeds the limits: 100 items, 1 MiB of text per item, 8 MiB of text per request, 1,024 bytes per `title`, 4,000 characters of `instructions`, 16 KiB of `attributes` or 1 KiB of `custom_attributes` per item. ### Empty query results diff --git a/api-reference/v2/index.mdx b/api-reference/v2/index.mdx index f856d249..241cea0d 100644 --- a/api-reference/v2/index.mdx +++ b/api-reference/v2/index.mdx @@ -6,7 +6,7 @@ description: "Single reference to all HydraDB endpoints" ## Quick links - **New to HydraDB?** Start with the [Quickstart](/get-started/v2/quickstart) -- **Prefer SDKs?** See [SDKs - Node and Python](/api-reference/v2/sdks) +- **Prefer SDKs?** See [SDKs for Node and Python](/api-reference/v2/sdks) - **Authentication:** Every endpoint requires `Authorization: Bearer ` - **Base URL:** `https://api.hydradb.com` - **Errors:** See [Error Responses](/api-reference/v2/error-responses) diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index 82115a48..055a7d33 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -5224,14 +5224,16 @@ "x-deprecated": "true" }, "type": { - "description": "Type names the corpus: knowledge (default) or memory.", + "deprecated": true, + "description": "Deprecated: kept for split databases.\nType names the corpus: knowledge (default) or memory.", "enum": [ "knowledge", "memory", "all" ], "example": "knowledge", - "type": "string" + "type": "string", + "x-deprecated": true } }, "type": "object" @@ -5737,6 +5739,20 @@ "instructions": { "type": "string" }, + "sub_tenant_id": { + "deprecated": true, + "description": "deprecated: use collection", + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + }, + "tenant_id": { + "deprecated": true, + "description": "deprecated: use database", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + }, "upsert": { "description": "Upsert, Enrich and Instructions are the request-level defaults for the\nitem-level fields of the same name: true, true and \"\" when absent.", "example": "true", @@ -6595,6 +6611,10 @@ ], "type": "string" }, + "received_at": { + "description": "When the context this chunk belongs to was received (RFC 3339). This is the ingest time, not the caller's happened_at, which is not echoed here. Omitted when the store holds no receipt time for the row (older rows); it is never sent empty.", + "type": "string" + }, "score": { "description": "Relevance after reranking.", "type": "number" @@ -7004,7 +7024,9 @@ }, "type": { "$ref": "#/components/schemas/search.SourceType", - "description": "Corpus to query: knowledge (the default), memory, or all (both, merged)." + "deprecated": true, + "description": "Deprecated: kept for split databases. Corpus to query: knowledge (the default), memory, or all (both, merged).", + "x-deprecated": true } }, "type": "object" @@ -7013,7 +7035,7 @@ "description": "The four-key /query response body: chunks, graph, forceful_relations and llm_prompt, and nothing else.", "properties": { "chunks": { - "description": "Retrieved chunks, ranked. Each carries its own text and enrichment, and nothing about its source: POST /context/list with its context_id in `ids` returns the source's title, type, collection, timestamp and metadata.", + "description": "Retrieved chunks, ranked. Each carries its own text, enrichment and received_at, and nothing else about its source: POST /context/list with its context_id in `ids` returns the source's title, type, collection and metadata.", "items": { "$ref": "#/components/schemas/search.QueryChunk" }, @@ -8209,14 +8231,16 @@ "x-deprecated": "true" }, "type": { - "description": "Type names the corpus: knowledge (default) or memory.", + "deprecated": true, + "description": "Deprecated: kept for split databases.\nType names the corpus: knowledge (default) or memory.", "enum": [ "knowledge", "memory", "all" ], "example": "knowledge", - "type": "string" + "type": "string", + "x-deprecated": true } }, "type": "object" @@ -10623,16 +10647,18 @@ "style": "form" }, { - "description": "Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", + "description": "Deprecated: kept for split databases. Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", "in": "query", "name": "type", "schema": { + "deprecated": true, "enum": [ "knowledge", "memory", "all" ], - "type": "string" + "type": "string", + "x-deprecated": "true" } }, { @@ -10770,12 +10796,14 @@ "x-deprecated": "true" }, "type": { + "deprecated": true, "enum": [ "knowledge", "memory" ], "title": "type", - "type": "string" + "type": "string", + "x-deprecated": "true" }, "upsert": { "default": "true", @@ -10790,7 +10818,7 @@ } } }, - "description": "Context[] body: the application/json alternative to this form. | Corpus to write to: 'knowledge' (default) or 'memory'. 'all' is refused here: an ingest must name the one corpus it writes to. | 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) | Deprecated: knowledge files to ingest (repeatable; type=knowledge, split databases only) | Deprecated: per-document metadata as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Deprecated: app-knowledge items as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB, optional acl principal list (PRO-1684). | Deprecated: memory items as a JSON array (type=memory, split databases only); use context. Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Contexts as a JSON array, the same list a JSON body carries under `context`. Each is one of text | conversation ([{role, content}]), with optional context_id, title (\u003c= 1024 bytes), user_name, enrich, upsert, instructions (\u003c= 4000 chars), happened_at, attributes, custom_attributes, context_category (auto|user_preference|business_knowledge|decision_trace), forceful_relations ({context_ids, properties}), acl. At most 100 contexts, 1 MiB of text per context and 8 MiB per request. Unknown keys are refused. Contexts land in the memory corpus. | Request-level enrichment default for `context` (true/false/1/0) | Request-level enrichment instructions default for `context` (\u003c= 4000 chars) | Optional bring-your-own-graph payload as JSON, keyed by context_id (context) or source_id (split paths)", + "description": "Context[] body: the application/json alternative to this form. | Deprecated: kept for split databases. Corpus to write to: 'knowledge' (default) or 'memory'. 'all' is refused here: an ingest must name the one corpus it writes to. | 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) | Deprecated: knowledge files to ingest (repeatable; type=knowledge, split databases only) | Deprecated: per-document metadata as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Deprecated: app-knowledge items as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB, optional acl principal list (PRO-1684). | Deprecated: memory items as a JSON array (type=memory, split databases only); use context. Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Contexts as a JSON array, the same list a JSON body carries under `context`. Each is one of text | conversation ([{role, content}]), with optional context_id, title (\u003c= 1024 bytes), user_name, enrich, upsert, instructions (\u003c= 4000 chars), happened_at, attributes, custom_attributes, context_category (auto|user_preference|business_knowledge|decision_trace), forceful_relations ({context_ids, properties}), acl. At most 100 contexts, 1 MiB of text per context and 8 MiB per request. Unknown keys are refused. Contexts land in the memory corpus. | Request-level enrichment default for `context` (true/false/1/0) | Request-level enrichment instructions default for `context` (\u003c= 4000 chars) | Optional bring-your-own-graph payload as JSON, keyed by context_id (context) or source_id (split paths)", "required": true }, "responses": { @@ -11226,16 +11254,18 @@ } }, { - "description": "Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", + "description": "Deprecated: kept for split databases. Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", "in": "query", "name": "type", "schema": { + "deprecated": true, "enum": [ "knowledge", "memory", "all" ], - "type": "string" + "type": "string", + "x-deprecated": "true" } }, { @@ -11464,16 +11494,18 @@ } }, { - "description": "Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", + "description": "Deprecated: kept for split databases. Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", "in": "query", "name": "type", "schema": { + "deprecated": true, "enum": [ "knowledge", "memory", "all" ], - "type": "string" + "type": "string", + "x-deprecated": "true" } }, { @@ -11682,16 +11714,18 @@ } }, { - "description": "Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", + "description": "Deprecated: kept for split databases. Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", "in": "query", "name": "type", "schema": { + "deprecated": true, "enum": [ "knowledge", "memory", "all" ], - "type": "string" + "type": "string", + "x-deprecated": "true" } }, { diff --git a/essentials/v2/api-results.mdx b/essentials/v2/api-results.mdx index bd36a856..433c6795 100644 --- a/essentials/v2/api-results.mdx +++ b/essentials/v2/api-results.mdx @@ -166,6 +166,7 @@ Render a UI, rerank, or apply your own rules from the three structured keys. The | `chunks[].content` | The matched text, verbatim. | | `chunks[].enrichment` | What enrichment extracted from that chunk (a preference, a fact), as a string. | | `chunks[].score` | Relevance, for your own thresholds. | +| `chunks[].received_at` | When HydraDB received the item, as an RFC 3339 timestamp; omitted when none is recorded. Not the item's `happened_at`. | | `graph[].path_summary` | One sentence per graph path; `graph[].triplets` for the steps and `graph[].origin` for how it was found. To show a path under its chunk, see [Attaching graph paths to chunks](/essentials/v2/query#attaching-graph-paths-to-chunks). | | `forceful_relations[]` | Chunks linked at ingest with `forceful_relations`, each with the `via` link that brought it in. | @@ -200,7 +201,7 @@ for (const rel of result.data.forcefulRelations) { ## 4. Showing source details -A chunk carries only `chunk_id`, `context_id`, `score`, `content`, `enrichment`, `enrichment_kind` and `temporal`. It has no title, url, collection, timestamps or attributes. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show an item's title, timestamp or attributes in your own UI, list it by its `context_id` with [`POST /context/list`](/api-reference/v2/endpoint/list-documents): +A chunk carries only `chunk_id`, `context_id`, `score`, `content`, `enrichment`, `enrichment_kind`, `received_at` (when HydraDB received the item) and `temporal`. It has no title, url, collection or attributes. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show an item's title, timestamp or attributes in your own UI, list it by its `context_id` with [`POST /context/list`](/api-reference/v2/endpoint/list-documents): ```bash curl -X POST 'https://api.hydradb.com/context/list' \ diff --git a/essentials/v2/bring-your-own-graph.mdx b/essentials/v2/bring-your-own-graph.mdx index 24e2e068..56fde875 100644 --- a/essentials/v2/bring-your-own-graph.mdx +++ b/essentials/v2/bring-your-own-graph.mdx @@ -26,7 +26,7 @@ Pick the right tool: | HydraDB to discover relationships for you | [Context graphs](/essentials/v2/context-graphs) (auto-extraction, the default) | | To declare links **between whole items** | `forceful_relations` on an item. See [Declared relations](/essentials/v2/ingest#10-declared-relations). | | To supply the **full entity and relation graph for one item** | **Bring Your Own Graph** (this page) | -| A standalone property graph you write and read with **Cypher**, separate from context items | [Cypher graph collections](/essentials/v2/graph-collections-byog) | +| A standalone property graph you write and read with **Cypher**, separate from context items | [Cypher Graph Collections](/essentials/v2/graph-collections-byog) | --- diff --git a/essentials/v2/databases-and-collections.mdx b/essentials/v2/databases-and-collections.mdx index df529581..9e5f63f4 100644 --- a/essentials/v2/databases-and-collections.mdx +++ b/essentials/v2/databases-and-collections.mdx @@ -15,8 +15,8 @@ description: "How HydraDB scopes data using databases and collections, and how s HydraDB scopes data using two identifiers: -- **`database`** - the top-level scoping identifier. Use it for customers, environments, or other primary data boundaries. -- **`collection`** - an optional scoping identifier within a database. Use it for users, workspaces, teams, or other logical partitions. +- **`database`**: the top-level scoping identifier. Use it for customers, environments, or other primary data boundaries. +- **`collection`**: an optional scoping identifier within a database. Use it for users, workspaces, teams, or other logical partitions. HydraDB write and query operations are scoped by `database`. When `collection` is provided, it further narrows the scope for that operation. If you omit `collection`, HydraDB uses the database's default collection. @@ -304,6 +304,8 @@ If you send **both** a canonical field and its deprecated alias: - **Same value** (for example `database` and `tenant_id` both `"acme"`): accepted. HydraDB uses the canonical value. - **Different values** (for example `database: "acme"` and `tenant_id: "other"`): rejected with `400`, since the two names refer to the same thing and must agree. Send only one. The same rule applies to `collection`/`sub_tenant_id` and to `collections`/`sub_tenant_ids` on `/query`. +In a JSON body each of `database`, `collection`, `tenant_id` and `sub_tenant_id` must be a string when present. A number or any other non-string value is a `400` that names the field; `null` counts as not sent. + --- ## Related diff --git a/essentials/v2/graph-collections-byog.mdx b/essentials/v2/graph-collections-byog.mdx index 87951468..b5d5416c 100644 --- a/essentials/v2/graph-collections-byog.mdx +++ b/essentials/v2/graph-collections-byog.mdx @@ -1,16 +1,22 @@ --- -title: "Bring Your Own Graph (BYOG)" -sidebarTitle: "Cypher graph collections" -description: "BYOG: full Cypher access to graph collections you own end-to-end." +title: "Cypher Graph Collections" +description: "Full Cypher access to property graphs you own end to end, stored and run by HydraDB." --- -Bring Your Own Graph (BYOG) gives you full **Cypher** access to graph -collections that you own end-to-end: you model the schema, you write the +Cypher Graph Collections give you full **Cypher** access to graph +collections that you own end to end: you model the schema, you write the queries, HydraDB runs and stores them. It is built for teams migrating an existing property-graph workload (for example from Neo4j) who want to keep -their Cypher and their data model as-is. +their Cypher and their data model as they are. -- **Databases** group your collections. A BYOG database appears in your + + Cypher Graph Collections are separate from context items. The endpoints live + under the `/byog` path, but they are not [Bring Your Own Graph](/essentials/v2/bring-your-own-graph), + which attaches your own entities and relations to a context item at ingest + with `graph_payload`. + + +- **Databases** group your collections. A graph database created here appears in your dashboard and in the standard database APIs like any other HydraDB database. - **Collections** are independent graphs inside a database. Queries run against exactly one collection; collections never see each other's data. @@ -19,7 +25,7 @@ their Cypher and their data model as-is. variable-length traversal, relationship expansion, and shortest-path finding. Your query is sent verbatim; HydraDB never rewrites it. - **Isolation is structural.** Each collection is a completely separate graph - owned by your organization. There is no cross-tenant data to reach: + owned by your organization. There is no other organization's data to reach: `MATCH (n) RETURN n` returns *your* nodes and nothing else. ## Quickstart @@ -33,7 +39,7 @@ curl -X POST "$BASE/byog/databases" \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"database": "crm"}' -# 2. Write data - collections auto-create on first use +# 2. Write data. Collections are created on first use. curl -X POST "$BASE/byog/query" \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{ diff --git a/essentials/v2/ingest.mdx b/essentials/v2/ingest.mdx index 0dd6d0e3..9f821a38 100644 --- a/essentials/v2/ingest.mdx +++ b/essentials/v2/ingest.mdx @@ -116,7 +116,7 @@ The response is `202 Accepted`: "failed_count": 0 }, "error": null, - "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d" } + "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", "api_version": "2.0.1" } } ``` @@ -133,13 +133,13 @@ A `202` means the items were accepted and queued, not that they are searchable y | Field | Notes | | --- | --- | -| `database` | Required. The database to write to. | -| `collection` | Optional. The collection to write to; the default collection when omitted. | +| `database` | Required. The database to write to. `tenant_id` is its deprecated alias. | +| `collection` | Optional. The collection to write to; the default collection when omitted. `sub_tenant_id` is its deprecated alias. | | `context` | The list of items, at most 100. | | `enrich` | Request-level default for every item's `enrich`. Default `true`. | | `upsert` | Request-level default for every item's `upsert`. Default `true`. | | `instructions` | Request-level default for every item's `instructions`. Default empty. | -| `graph_payload` | Optional. A graph you built yourself, keyed by `context_id`. See [Bring your own graph](#11-bring-your-own-graph). | +| `graph_payload` | Optional. A graph you built yourself, keyed by `context_id`. See [Bring Your Own Graph](#11-bring-your-own-graph). | The three request-level defaults apply to any item that does not set the field itself, so one call can enrich some items and store others verbatim, or replace some items and append others. @@ -158,7 +158,7 @@ Each item is exactly one of `text` or `conversation`. | `enrich` | Extract entities, relations and preferences from this item. Default: the request's `enrich`, else `true`. | | `upsert` | Replace an existing item with the same `context_id`. Default: the request's `upsert`, else `true`. | | `instructions` | Steer enrichment for this item. At most 4,000 characters. Default: the request's `instructions`. | -| `happened_at` | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately. | +| `happened_at` | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately and returns that as `received_at` on query chunks. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. See [Attributes](/essentials/v2/attributes). | | `custom_attributes` | Free-form fields. Not filterable with `attributes`. | | `forceful_relations` | Relations you declare to other items: `{ "context_ids": ["chat-w1"], "properties": {} }`, where `context_ids` are the `context_id`s of the related items. See [Declared relations](#10-declared-relations). | @@ -168,6 +168,7 @@ Each item is exactly one of `text` or `conversation`. ### Limits and unrecognised fields - At most **100 items** per request, **1 MiB** of text per item, and **8 MiB** of text per request. +- The whole request body is capped at **16 MiB**: the JSON body, or the `context` form field when an SDK sends a multipart form. A larger one is refused with `413`. - `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each item. - A validation error names the item it refers to as `context[N]`. - An unrecognised field is a `400`, on the request, on an item, on a conversation turn or inside `forceful_relations`. The error names the field and lists the accepted ones. @@ -260,7 +261,7 @@ Use `instructions` to steer extraction. Set it on the request to apply it to eve ## 8. Time -`happened_at` is when the item is about: the meeting date, the decision date, the day a preference was stated. HydraDB records when it received the item separately. Set `happened_at` whenever it differs from ingest time, so recency and temporal reasoning at query time use the right date. +`happened_at` is when the item is about: the meeting date, the decision date, the day a preference was stated. HydraDB records when it received the item separately, and query chunks return that receipt time as `received_at`. Set `happened_at` whenever it differs from ingest time, so recency and temporal reasoning at query time use the right date. --- @@ -294,7 +295,7 @@ Any item, text or conversation, can declare which other items it relates to: --- -## 11. Bring your own graph +## 11. Bring Your Own Graph Skip extraction for an item and supply its entities and relations yourself with `graph_payload`, a map keyed by `context_id`: @@ -318,7 +319,7 @@ Skip extraction for an item and supply its entities and relations yourself with } ``` -Every key in `graph_payload` must match the `context_id` of an item in the same request; a key that matches nothing is a `400`, so a typo cannot silently drop a graph. A keyed item is still chunked and embedded, so it stays searchable. The entity and relation shapes, caps and replace semantics are on [Bring your own graph](/essentials/v2/bring-your-own-graph). +Every key in `graph_payload` must match the `context_id` of an item in the same request; a key that matches nothing is a `400`, so a typo cannot silently drop a graph. A keyed item is still chunked and embedded, so it stays searchable. The entity and relation shapes, caps and replace semantics are on [Bring Your Own Graph](/essentials/v2/bring-your-own-graph). --- @@ -424,4 +425,4 @@ A `202` means queued. Poll status until `graph_creation` or `completed` before e - [Query](/essentials/v2/query) - [Attributes](/essentials/v2/attributes) - [Ingest context API reference](/api-reference/v2/endpoint/ingest-context) -- [Bring your own graph](/essentials/v2/bring-your-own-graph) +- [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) diff --git a/essentials/v2/query.mdx b/essentials/v2/query.mdx index 77ff2348..b5fd6022 100644 --- a/essentials/v2/query.mdx +++ b/essentials/v2/query.mdx @@ -66,6 +66,7 @@ The response `data` is exactly these four keys, inside the usual envelope: "score": 0.91, "content": "Refunds are processed within 30 days of purchase by the Finance Department.", "enrichment": "Refund window is 30 days; Finance owns refund processing.", + "received_at": "2026-07-02T09:14:05Z", "temporal": [ { "content": "Refund policy effective_from June 2026. Start: 2026-06-01", @@ -79,7 +80,8 @@ The response `data` is exactly these four keys, inside the usual envelope: "context_id": "chat-2026-07-29", "score": 0.84, "content": "user: Keep refund answers short please\nassistant: Got it.", - "enrichment": "User prefers short answers about refunds." + "enrichment": "User prefers short answers about refunds.", + "received_at": "2026-07-29T16:40:12Z" } ], "graph": [ @@ -238,10 +240,11 @@ The matched pieces of your items, ranked. Preserve the order. | `content` | string | The chunk's own text, verbatim. Enrichment is not concatenated into it. | | `enrichment` | string | What enrichment extracted from this chunk: the extracted statement (a preference, a fact). Omitted when enrichment extracted nothing. | | `enrichment_kind` | string | An optional label; omitted when none was set. | +| `received_at` | string | When HydraDB received the item this chunk came from, as an RFC 3339 timestamp (for example `2026-07-02T09:14:05Z`). This is the ingest time, not the item's `happened_at`, which is not returned here. Omitted when no receipt time is recorded for the chunk, as on older items; it is never sent empty. | | `temporal` | array | Present only when the query engaged temporal reasoning. Each entry is `{ content, start_date, end_date }`: `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` (only the dated sides are printed), and either date may be `null`. | -**Chunks carry nothing about their source.** No title, url, collection, timestamps or attributes. `llm_prompt` prints the title, collection, type, last-updated date and url for the model. To show an item's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. +**Chunks carry almost nothing about their source.** A chunk has no title, url, collection or attributes; the one source detail it carries is `received_at`. `llm_prompt` prints the title, collection, type, last-updated date and url for the model. To show an item's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. ### `graph[]` diff --git a/mintlify-hygiene.toml b/mintlify-hygiene.toml index a5ce1587..d5d2dba7 100644 --- a/mintlify-hygiene.toml +++ b/mintlify-hygiene.toml @@ -5,9 +5,6 @@ include = ["**/*.mdx"] exclude = [ "archive/**", "snippets/**", - # Intentionally hidden from nav (private, reachable only by direct URL) per PR #141; - # excluded so the nav_registration rule does not flag it. - "essentials/v2/graph-collections-byog.mdx", # Deprecated knowledge and memory pages (PRO-1618): the whole v1 version, # the v2 split-database pages and the cookbooks. Still live by URL, marked # noindex and deprecated, and out of nav on purpose. diff --git a/plugins/cli.mdx b/plugins/cli.mdx index 95063e15..db1fa75d 100644 --- a/plugins/cli.mdx +++ b/plugins/cli.mdx @@ -364,7 +364,7 @@ done `query` returns the response body as the server sent it: `chunks` (each with `chunk_id`, `context_id`, `score` and `content`, plus `enrichment`, - `enrichment_kind` and `temporal` when present), `graph`, `forceful_relations` and + `enrichment_kind`, `received_at` and `temporal` when present), `graph`, `forceful_relations` and `llm_prompt`. `list` returns the listed items under `sources`, each with its `id`. diff --git a/plugins/mcp.mdx b/plugins/mcp.mdx index 9c91c933..fff9a6f7 100644 --- a/plugins/mcp.mdx +++ b/plugins/mcp.mdx @@ -611,9 +611,9 @@ default, so an agent working across several databases can find their names without asking you. If you approved the app with **other databases not allowed**, the list has one entry and any other name is refused. -### Graph tools (BYOG openCypher) +### Graph tools (Cypher Graph Collections) -HydraDB MCP also exposes property graph tools for querying and writing domain graphs in openCypher: +HydraDB MCP also exposes tools for querying and writing [Cypher Graph Collections](/essentials/v2/graph-collections-byog) in openCypher: - **`hydradb_graph_query`**: Run Cypher reads and writes (`CREATE`, `MERGE`, `MATCH`, traversals). - Parameters: `query` (string, required), `params` (object), `database` (string), `collection` (string), `max_rows` (number). From 2a33f93d2b62d89bb5d5b90ed1ec6063a903cb2c Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Wed, 23 Sep 2026 20:38:48 +0530 Subject: [PATCH 06/17] docs: received_at in the Query overview, 16 MiB cap on the Bring Your Own Graph page (PRO-1618) The Query overview's chunk list and the "no source details" rows now name received_at, and the Bring Your Own Graph limits say a JSON body, graph_payload included, is capped at 16 MiB. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/v2/endpoint/query-overview.mdx | 2 +- essentials/v2/api-results.mdx | 2 +- essentials/v2/bring-your-own-graph.mdx | 2 +- essentials/v2/query.mdx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index f9f40378..2d5d41b9 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -158,7 +158,7 @@ Use text query when literal wording matters: legal clauses, SKUs, error codes, I | Key | Contents | | --- | --- | -| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content`, optional `enrichment` (a string), `enrichment_kind` and `temporal`. No source details; call `POST /context/list` with the `context_id` in `ids` for those. | +| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content`, optional `enrichment` (a string), `enrichment_kind`, `received_at` (when HydraDB received the item) and `temporal`. No other source details; call `POST /context/list` with the `context_id` in `ids` for those. | | `graph[]` | Paths through the context graph, deduplicated across both origins and not capped: `origin` (`query_path` or `chunk_relation`), `triplets[]` and a `path_summary`, which is never empty. Each hop's `relation.chunk_id` names the chunk it came from, and `relation.timestamp` (Unix epoch seconds) is present when the edge has one; a `chunk_relation` path is only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk. | | `forceful_relations[]` | Chunks linked with `forceful_relations` at ingest, each with the `via` that brought it in. Followed only in `thinking` mode. | | `llm_prompt` | A server-built markdown string, ready to inject into a model call: results cited `[1]`, forceful relations `[R1]`, related facts labelled `[P1]` in `graph[]` order with each path's relevance when it has one, then temporal facts and sources. | diff --git a/essentials/v2/api-results.mdx b/essentials/v2/api-results.mdx index 433c6795..be7f8531 100644 --- a/essentials/v2/api-results.mdx +++ b/essentials/v2/api-results.mdx @@ -233,7 +233,7 @@ Fetch it lazily, when a citation is opened, rather than for every chunk on every | --- | --- | --- | | Building your own context string | Duplicates what the server already did, without the labels | Inject `llm_prompt`. | | Passing the raw `data` object to the model | Wastes tokens on ids and scores | Inject `llm_prompt`. | -| Expecting a title or url on a chunk | Chunks carry no source details | Read them from `llm_prompt`, or call `POST /context/list` with `ids: [""]`. | +| Expecting a title or url on a chunk | Chunks carry no title, url, collection or attributes | Read them from `llm_prompt`, or call `POST /context/list` with `ids: [""]`. | | Concatenating `content` and `enrichment` | Enrichment is stored separately on purpose | Use `content` for what was said, `enrichment` for what was extracted. | | Re-sorting chunks client-side | Overrides HydraDB's ranking | Preserve the returned order. | | Missing a grounding instruction | The model invents answers when retrieval is thin | System prompt: answer only from the provided context. | diff --git a/essentials/v2/bring-your-own-graph.mdx b/essentials/v2/bring-your-own-graph.mdx index 56fde875..dfbc854d 100644 --- a/essentials/v2/bring-your-own-graph.mdx +++ b/essentials/v2/bring-your-own-graph.mdx @@ -92,7 +92,7 @@ In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, wh | Relation `context` length | ≤ 2,000 bytes (UTF-8) | | Entity key, `name`, `type`, `namespace`, `identifier`, `predicate` and `temporal_details` length | ≤ 256 bytes (UTF-8) each | -The request itself keeps the normal ingest limits: at most 100 items, 1 MiB of text per item and 8 MiB of text per request. See [Ingest context](/essentials/v2/ingest#limits-and-unrecognised-fields). +The request itself keeps the normal ingest limits: at most 100 items, 1 MiB of text per item and 8 MiB of text per request. A JSON body, `graph_payload` included, is capped at 16 MiB (`413` beyond it), so split very large graphs across requests. See [Ingest context](/essentials/v2/ingest#limits-and-unrecognised-fields). --- diff --git a/essentials/v2/query.mdx b/essentials/v2/query.mdx index b5fd6022..dce84f4b 100644 --- a/essentials/v2/query.mdx +++ b/essentials/v2/query.mdx @@ -468,7 +468,7 @@ Most of the time the defaults are right. When they are not, here is where to sta | `forceful_relations` is `[]` | Nothing in the hits declared `forceful_relations`, `follow_forceful_relations: false`, or the query ran in `fast` mode | Declare relations at ingest, leave the flag on, and use `mode: "thinking"`. | | Recent items do not appear | Indexing not finished | Poll `GET /context/status?ids=...&database=...`; chunks are invisible until processing reaches at least `graph_creation`. | | `attributes` does not narrow results | The key is not declared in `database_metadata_schema`, or the value does not match | Declare the field and send it in `attributes` at ingest; filter with an operator such as `$eq`. `attributes` cannot filter on `custom_attributes`. | -| Chunk has no title or url | Chunks carry no source details by design | Read them from `llm_prompt`, or call `POST /context/list` with `ids: [""]`. | +| Chunk has no title or url | Chunks carry no title, url, collection or attributes by design | Read them from `llm_prompt`, or call `POST /context/list` with `ids: [""]`. | | `operator: "phrase"` ignored | `query_by` is not `"text"` | `operator` only applies to BM25 text query. | --- From adce073358b4aad2e8c2f1449d4eba48ee683a37 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Wed, 23 Sep 2026 23:44:57 +0530 Subject: [PATCH 07/17] docs: bind every v2 endpoint page to the OpenAPI spec, drop "items" wording (PRO-1618) The ten endpoint pages that carried `api:` plus `playground: "none"` (create/list/status/stats databases, ingest, list, delete, relations, subgraph, query) now use `openapi: "api-reference/v2/openapi.json ..."` like every other endpoint page, so they render the generated request and response sections and the Try it playground. The spec is unchanged: it is byte-identical to app staging. Hand-written parameter and response tables the generated sections now show are removed. Facts the spec does not carry (limits, defaults, name rules, the ingest context fields, Bring Your Own Graph) stay as short notes. Query and List Context keep a hand-written Response section, because the published /query response is a union of the old v2 body and the four-key body. Subgraph's example now uses the query-string form the page is bound to. Every visible page stops calling ingested context "items": the list is `context`, each entry a context. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 84 ++++++------ api-reference/v2/endpoint/create-tenant.mdx | 41 ++---- .../v2/endpoint/delete-collection.mdx | 4 +- api-reference/v2/endpoint/delete-source.mdx | 37 +----- api-reference/v2/endpoint/delete-tenant.mdx | 4 +- api-reference/v2/endpoint/fetch-content.mdx | 4 +- api-reference/v2/endpoint/ingest-context.mdx | 83 +++++------- api-reference/v2/endpoint/list-documents.mdx | 72 +++++----- api-reference/v2/endpoint/list-tenants.mdx | 16 +-- api-reference/v2/endpoint/query-overview.mdx | 2 +- api-reference/v2/endpoint/query.mdx | 63 ++++----- .../v2/endpoint/source-relations.mdx | 49 +------ api-reference/v2/endpoint/source-status.mdx | 14 +- .../v2/endpoint/sources-overview.mdx | 22 ++-- api-reference/v2/endpoint/subgraph.mdx | 66 ++-------- api-reference/v2/endpoint/tenant-stats.mdx | 25 +--- api-reference/v2/endpoint/tenant-status.mdx | 26 +--- api-reference/v2/error-responses.mdx | 4 +- api-reference/v2/index.mdx | 18 +-- api-reference/v2/sdks.mdx | 16 +-- essentials/v2/access-control.mdx | 4 +- essentials/v2/api-results.mdx | 8 +- essentials/v2/architecture.mdx | 16 +-- essentials/v2/attributes.mdx | 30 ++--- essentials/v2/bring-your-own-graph.mdx | 24 ++-- essentials/v2/connectors.mdx | 2 +- essentials/v2/context-graphs.mdx | 10 +- essentials/v2/databases-and-collections.mdx | 10 +- essentials/v2/glossary.mdx | 8 +- essentials/v2/graph-collections-byog.mdx | 2 +- essentials/v2/ingest.mdx | 124 +++++++++--------- essentials/v2/query.mdx | 24 ++-- essentials/v2/webhooks.mdx | 4 +- get-started/v2/core-concepts.mdx | 20 +-- get-started/v2/introduction.mdx | 4 +- get-started/v2/quickstart.mdx | 20 +-- plugins/claude-code.mdx | 4 +- plugins/cli.mdx | 18 +-- plugins/mcp.mdx | 22 ++-- 39 files changed, 397 insertions(+), 607 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index 1d41494e..4188b578 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -7,13 +7,13 @@ description: "LLM-facing reference for building against the current HydraDB API This document is a self-contained reference for AI coding agents integrating HydraDB into a project. -HydraDB stores **context**: text and conversations that you send as items. It chunks, embeds and enriches every item, extracts entities and relations into a context graph, and answers questions over all of it through one query endpoint that returns ranked chunks, graph paths and a prompt-ready string. +HydraDB stores **context**: text and conversations that you send in a `context` list. It chunks, embeds and enriches every context, extracts entities and relations into a context graph, and answers questions over all of it through one query endpoint that returns ranked chunks, graph paths and a prompt-ready string. ### TL;DR: Critical endpoints 1. **`POST /databases`** (`client.databases.create()`): Create an isolated database. A name is all it needs. 2. **`GET /databases/status`** (`client.databases.status()`): Poll until `data.infra.ready_for_ingestion` is `true`. -3. **`POST /context/ingest`** (`client.context.ingest()`): Send a `context` list of items. Each item is either a `text` or a `conversation`. +3. **`POST /context/ingest`** (`client.context.ingest()`): Send a `context` list. Each entry is either a `text` or a `conversation`. 4. **`GET /context/status`** (`client.context.status()`): Poll with `ids` until `indexing_status` is `graph_creation` or `completed` (searchable), or `errored`. 5. **`POST /query`** (`client.query()`): Ask a question. The response `data` has exactly four keys: `chunks`, `graph`, `forceful_relations` and `llm_prompt`. Inject `llm_prompt` into your model call verbatim. 6. **`POST /feedback`** (`client.feedback.submit()`): Tell us when a query did not give you what you needed, and once the task is done, how the context held up. See [Sending feedback](#sending-feedback). @@ -67,11 +67,11 @@ Core raw HTTP responses (`/databases`, `/context/*`, `/query` and `/feedback`) a | Check indexing | `GET /context/status` · `client.context.status()` | | Search | `POST /query` · `client.query()` | | Report back on query results | `POST /feedback` · `client.feedback.submit()` | -| List items | `POST /context/list` · `client.context.list()` | +| List context | `POST /context/list` · `client.context.list()` | | Read an item's stored content | `GET /context/inspect` · `client.context.inspect()` | -| Delete items | `DELETE /context` · `client.context.delete()` | +| Delete context | `DELETE /context` · `client.context.delete()` | | Inspect graph relations | `GET /context/relations` · `client.context.relations()` | -| Walk an item's connected items | `GET /context/{id}/subgraph` · `client.context.subgraph()` | +| Walk the context connected to one context | `GET /context/{id}/subgraph` · `client.context.subgraph()` | | Edit an indexed item's attributes | `PATCH /context/{id}/metadata` · Python `client.context.update_source_metadata()` / TS `client.context.updateSourceMetadata()` | | Indexing webhooks | `/webhooks/indexing*` | @@ -204,16 +204,16 @@ Recommended patterns: | Shared company context | a shared collection such as `company`, queried together with the user's collection through `collections` | | Per-user context (preferences, conversation history) | `collection = user_id` | -### Context items +### What goes in `context` -An item is one piece of context, and carries exactly one of: +Each entry in the `context` list is one piece of context, and carries exactly one of: - `text`: a document, a note, a policy, an agent log line; anything you already have as a string. To ingest a file, extract its text first. - `conversation`: a list of `{ role, content }` turns, the same message list you already send to OpenAI or Anthropic. -Every item can also carry a `context_id` (your id; reuse it to replace the item), a `title`, declared `attributes` and free-form `custom_attributes`, a `happened_at` date, `forceful_relations` to other items, an `acl`, and per-item `enrich` / `upsert` / `instructions`. See [Item fields](#item-fields). +Every context can also carry a `context_id` (your id; reuse it to replace the context), a `title`, declared `attributes` and free-form `custom_attributes`, a `happened_at` date, `forceful_relations` to other contexts, an `acl`, and its own `enrich` / `upsert` / `instructions`. See [Context fields](#context-fields). -With `enrich: true` (the default) HydraDB reads each item and extracts entities, relations and preferences into the context graph. The extracted statement comes back on each chunk as `enrichment`, separate from the chunk's verbatim `content`. +With `enrich: true` (the default) HydraDB reads each context and extracts entities, relations and preferences into the context graph. The extracted statement comes back on each chunk as `enrichment`, separate from the chunk's verbatim `content`. ### Query @@ -254,7 +254,7 @@ Chunks remain the primary output; the graph explains how they connect. ### Forceful relations -At ingest, any item (text or conversation) can declare which other items it is linked to: +At ingest, any context (text or conversation) can declare which other contexts it is linked to: ```json { @@ -317,7 +317,7 @@ SDK naming: - Python methods and fields: snake_case, for example `client.databases.collections()`, `max_results`, `query_by`, `result.data.llm_prompt`, `status.indexing_status`. - TypeScript methods and fields: camelCase, for example `maxResults`, `queryBy`, `pageSize`, `result.data.llmPrompt`, `chunk.chunkId`, `chunk.contextId`, `path.pathSummary`, `result.data.forcefulRelations`, `status.indexingStatus`. - Both SDKs return a `{ success, data, error, meta }` envelope; the payload is under `.data` (for example `response.data.infra`, `response.data.statuses`, `response.data.results`). -- `client.context.ingest()` sends a multipart form: the item list goes in the `context` form field as a JSON string. Keys inside each item stay snake_case in every language (`context_id`, `happened_at`, `custom_attributes`), because that string is raw wire data. +- `client.context.ingest()` sends a multipart form: the list goes in the `context` form field as a JSON string. Keys inside each context stay snake_case in every language (`context_id`, `happened_at`, `custom_attributes`), because that string is raw wire data. --- @@ -347,7 +347,7 @@ while True: time.sleep(5) # 3. Ingest a policy into the shared collection and a conversation into Alex's. -# The SDK sends the item list as a JSON string in the `context` form field. +# The SDK sends the list as a JSON string in the `context` form field. client.context.ingest( database=database, collection="company", @@ -371,7 +371,7 @@ client.context.ingest( }]), ) -# 4. Wait until both items are searchable. +# 4. Wait until both contexts are searchable. pending = {"company": "refund-policy", "user_alex": "chat-alex-001"} while pending: for collection, context_id in list(pending.items()): @@ -423,7 +423,7 @@ while (true) { } // 3. Ingest a policy into the shared collection and a conversation into Alex's. -// The SDK sends the item list as a JSON string in the `context` form field. +// The SDK sends the list as a JSON string in the `context` form field. await client.context.ingest({ database, collection: "company", @@ -447,7 +447,7 @@ await client.context.ingest({ }]), }); -// 4. Wait until both items are searchable. +// 4. Wait until both contexts are searchable. const pending = new Map([["company", "refund-policy"], ["user_alex", "chat-alex-001"]]); while (pending.size > 0) { for (const [collection, id] of pending) { @@ -526,7 +526,7 @@ curl -s -X POST "$API/context/ingest" "${AUTH[@]}" \ }] }" -# 4. Wait until both items are searchable. +# 4. Wait until both contexts are searchable. for pair in "company:refund-policy" "user_alex:chat-alex-001"; do COLLECTION="${pair%%:*}"; ID="${pair#*:}" while true; do @@ -551,7 +551,7 @@ curl -s -X POST "$API/query" "${AUTH[@]}" \ }' | jq '{chunks: [.data.chunks[]?.content], paths: [.data.graph[]?.path_summary], llm_prompt: .data.llm_prompt}' ``` -The response has four keys. `chunks` are the matched pieces of your items, ranked, each with a `score` and its `context_id`. `graph` is the list of relation paths connecting them, each with a one-sentence `path_summary`. `forceful_relations` holds items linked at ingest (none here). `llm_prompt` is all of that as one markdown string with citation labels, ready to drop into your model call: +The response has four keys. `chunks` are the matched pieces of your context, ranked, each with a `score` and its `context_id`. `graph` is the list of relation paths connecting them, each with a one-sentence `path_summary`. `forceful_relations` holds context linked at ingest (none here). `llm_prompt` is all of that as one markdown string with citation labels, ready to drop into your model call: ```python messages = [{"role": "system", "content": result.data.llm_prompt}, @@ -653,21 +653,21 @@ One endpoint takes every item, text or conversation, into any collection of a da |---|---| | `database` | Required. The database to write to. | | `collection` | Optional. The collection to write to; the default collection when omitted. | -| `context` | The list of items, at most 100. | +| `context` | The list of contexts, at most 100. | | `enrich` | Request-level default for every item's `enrich`. Default `true`. | | `upsert` | Request-level default for every item's `upsert`. Default `true`. | | `instructions` | Request-level default for every item's `instructions`. Default empty. | | `graph_payload` | Optional. A graph you built yourself, keyed by `context_id`. See [Bring Your Own Graph](#bring-your-own-graph). | -The request-level values apply to any item that does not set the field itself, so one call can enrich some items and store others as they are, or replace some items and append others. +The request-level values apply to any context that does not set the field itself, so one call can enrich some contexts and store others as they are, or replace some and append others. -### Item fields +### Context fields Each item carries exactly one of `text` or `conversation`. | Field | Notes | |---|---| -| `context_id` | Your id for the item. When omitted it is generated from the item's text and `title`, so two items without ids that have the same text and the same (or no) title collide. Must not contain commas. | +| `context_id` | Your id for the context. When omitted it is generated from its text and `title`, so two contexts without ids that have the same text and the same (or no) title collide. Must not contain commas. | | `title` | Optional readable name, printed in `llm_prompt` and matchable with `titles` on `/query`. At most 1,024 bytes. | | `text` | Plain text or markdown. | | `conversation` | A list of `{ role, content }` turns. | @@ -725,16 +725,16 @@ Every key in `graph_payload` must equal the `context_id` of an item in the same ### Limits and validation -- At most **100 items** per request, **1 MiB** of text per item, **8 MiB** of text per request. Split larger batches. +- At most **100 contexts** per request, **1 MiB** of text per context, **8 MiB** of text per request. Split larger batches. - The whole body is capped at **16 MiB** (the JSON body, or the `context` form field on the multipart form). A larger one is a `413` with `request body too large`. - `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each item. - `attributes` are capped at **16 KiB** and `custom_attributes` at **1 KiB** per item, measured on the compact JSON encoding in UTF-8 bytes (keys and punctuation count). - A validation error names the item it refers to as `context[N]`. -- Ingest takes text only. To ingest a PDF, DOCX or CMS export, extract its text in your application and send it as `text`, one item per document. For tools such as Slack, Notion or Google Drive, use a [connector](/essentials/v2/connectors): synced content lands in the same database and is queried together with your items. +- Ingest takes text only. To ingest a PDF, DOCX or CMS export, extract its text in your application and send it as `text`, one context per document. For tools such as Slack, Notion or Google Drive, use a [connector](/essentials/v2/connectors): synced content lands in the same database and is queried together with your own context. ### SDKs: the `context` form field -The SDKs send a multipart form rather than a JSON body. The item array goes in the `context` form field as a JSON string, next to `database`, `collection`, `upsert`, `enrich`, `instructions` and `graph_payload`; the server runs the same validation on both entry points. On the form, `upsert` and `enrich` are `"true"`, `"false"`, `"1"` or `"0"`, and any other value is a `400`. Python: `client.context.ingest(database=..., collection=..., context=json.dumps([...]))`. TypeScript: `await client.context.ingest({ database, collection, context: JSON.stringify([...]) })`. Keys inside each item stay snake_case in both. Full examples are in [Minimal end-to-end flow](#4-minimal-end-to-end-flow). +The SDKs send a multipart form rather than a JSON body. The array goes in the `context` form field as a JSON string, next to `database`, `collection`, `upsert`, `enrich`, `instructions` and `graph_payload`; the server runs the same validation on both entry points. On the form, `upsert` and `enrich` are `"true"`, `"false"`, `"1"` or `"0"`, and any other value is a `400`. Python: `client.context.ingest(database=..., collection=..., context=json.dumps([...]))`. TypeScript: `await client.context.ingest({ database, collection, context: JSON.stringify([...]) })`. Keys inside each context stay snake_case in both. Full examples are in [Minimal end-to-end flow](#4-minimal-end-to-end-flow). ### Response @@ -760,7 +760,7 @@ The SDKs send a multipart form rather than a JSON body. The item array goes in t - `results[].id` is the item's `context_id`: the one you sent, or the generated one. Pass it to `GET /context/status`. - `results[].infer` mirrors the item's `enrich`. -- `results[].status` is `queued` or `failed`. A failed item carries `error` and `error_code`; the other items in the request are still queued. +- `results[].status` is `queued` or `failed`. A failed context carries `error` and `error_code`; the others in the request are still queued. - A `202` means queued, not searchable. Poll status before querying. --- @@ -870,9 +870,9 @@ Rules: | `alpha` | `0.0` to `1.0`, or `"auto"` | Semantic versus keyword blend for `hybrid`. `1.0` is fully semantic, `0.0` fully BM25. Default `0.8`; `"auto"` also resolves to `0.8`. | | `recency_bias` | `0.0` to `1.0` | Boost newer content. Default `0.4`; `0` disables it. | | `ids` | `string[]` | Restrict retrieval to these `context_id`s. | -| `titles` | `string[]` | Restrict retrieval to items with one of these exact titles (case-insensitive). | +| `titles` | `string[]` | Restrict retrieval to context with one of these exact titles (case-insensitive). | | `attributes` | object | Filter on declared attributes with operators. See [Filtering with attributes](#filtering-with-attributes). | -| `acl` | `string[]` | Query on behalf of an identity: only items it may retrieve are returned. Omitted, empty or `["*"]` disables filtering. | +| `acl` | `string[]` | Query on behalf of an identity: only context it may retrieve is returned. Omitted, empty or `["*"]` disables filtering. | | `query_apps` | boolean | Default `true`: also search connector content by its app identity (exact ids, actors, threads), on top of normal retrieval. | | `graph_context` | boolean | Default `true`: include `graph[]`. | | `follow_forceful_relations` | boolean | Default `true`: pull declared forceful relations into `forceful_relations[]` (`thinking` mode only). | @@ -892,7 +892,7 @@ Rules: | Error codes, SKUs, product names | `query_by: "hybrid"`, `alpha: 0.3` to `0.5` | | Recent operational updates | `recency_bias` above the default `0.4`, plus an `attributes` filter on status or doc type | | Connector content (Slack, Jira, Gmail) | `mode: "thinking"` (`query_apps` is on by default) | -| Follow linked items | `mode: "thinking"` (`follow_forceful_relations` is on by default) | +| Follow linked context | `mode: "thinking"` (`follow_forceful_relations` is on by default) | | A known item or document | `ids: [...]` or `titles: [...]` | ### Examples @@ -997,7 +997,7 @@ Attribute-filtered search, on behalf of one user: } ``` -`chunks[]`: the matched pieces of your items, ranked. Preserve the order; it is the server ranking. +`chunks[]`: the matched pieces of your context, ranked. Preserve the order; it is the server ranking. | Field | Meaning | |---|---| @@ -1007,7 +1007,7 @@ Attribute-filtered search, on behalf of one user: | `content` | The chunk's own text, verbatim. Enrichment is never concatenated into it. | | `enrichment` | A plain string: what enrichment extracted from this chunk (a preference, a fact). Omitted when there is none. | | `enrichment_kind` | An optional label; omitted when none was set. | -| `received_at` | When HydraDB received the item, as an RFC 3339 timestamp. This is the ingest time, not the item's `happened_at` (which is not returned). Omitted when no receipt time is recorded, as on older items; never sent empty. | +| `received_at` | When HydraDB received the context, as an RFC 3339 timestamp. This is the ingest time, not its `happened_at` (which is not returned). Omitted when no receipt time is recorded, as on context ingested before it existed; never sent empty. | | `temporal` | Present only when the query engaged temporal reasoning: `{ content, start_date, end_date }` entries, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`. | Chunks carry almost nothing about their source: no title, url, collection or attributes, only `received_at`. `llm_prompt` prints the title, collection, last-updated date and url for the model. To read an item's stored content yourself, call `GET /context/inspect` with the chunk's `context_id`; to read its title and attributes, call `POST /context/list` with `ids: [context_id]`. @@ -1068,7 +1068,7 @@ Citation labels: | `[R1]`, `[R2]`, ... | Forceful relation `### R1.`, `### R2.`: that entry of `forceful_relations[]`. | | `[P1]`, `[P2]`, ... | A related fact: path 1, 2, ... of `graph[]`. | -A related fact or a temporal fact ends with the labels of the results it was extracted from. The numbers in `## Sources` count items, not results, and are not citation labels. +A related fact or a temporal fact ends with the labels of the results it was extracted from. The numbers in `## Sources` count sources, not results, and are not citation labels. A trimmed example: @@ -1277,7 +1277,7 @@ Rules: None of these calls takes anything beyond `database`, an optional `collection`, and the fields shown. Send the collection you ingested into. -### List items +### List context `POST /context/list` · `client.context.list()` @@ -1303,9 +1303,9 @@ Parameters: - `page` (1-indexed, default `1`), `page_size` (`1` to `100`, default `50`) - `filters`: exact-match constraints, ANDed (`source_fields.title` matches a case-insensitive prefix). `source_fields` matches built-in fields such as `title`, `url`, `timestamp` and, for connector content, `app_provider`, `app_kind`, `app_external_id`, `app_parent_id`. The list filter keeps its own wire names for the two attribute maps: `filters.metadata` matches declared `attributes` and `filters.additional_metadata` matches `custom_attributes`. - `include_fields`: projection, for example `["title", "timestamp"]`. `content` and `url` are not projectable (a `400`); read an item's content with `GET /context/inspect`. -- `acl`: list as an identity; only items it may see are returned. +- `acl`: list as an identity; only context it may see is returned. -Response `data`: `{ success, message, sources: [...], total, pagination }`. `sources` is the wire name for the listed items: one row per item with its `id`, title, timestamp and stored attributes, without content. `pagination` carries `page`, `page_size`, `total`, `total_pages`, `has_next` and `has_previous`. +Response `data`: `{ success, message, sources: [...], total, pagination }`. `sources` is the wire name for the listed context: one row per context with its `id`, title, timestamp and stored attributes, without content. `pagination` carries `page`, `page_size`, `total`, `total_pages`, `has_next` and `has_previous`. ### Fetch an item's stored content @@ -1344,13 +1344,13 @@ const relations = await client.context.relations({ Returns `relations[]`, triplet groups with their evidence (predicate, the sentence it came from, `chunk_id`, confidence). Omit `id` for relations across the whole collection. Page with `cursor`: pass back `next_cursor` until it is `null`. Use it for graph debugging and provenance. -### Walk connected items +### Walk connected context `GET /context/{id}/subgraph` · `client.context.subgraph()` (the SDKs call the query-string form, `GET /context/subgraph`, which also accepts an id containing `/`) Returns every item reachable from one item through item-level links (declared forceful relations, a shared thread, parent and child), breadth-first up to `depth` hops, with the relations among them. An unknown id returns an empty subgraph, not an error. -### Delete items +### Delete context `DELETE /context` · `client.context.delete()` @@ -1464,13 +1464,13 @@ Most production integrations share one shape: 1. Create a database, and declare in `database_metadata_schema` the attributes you will filter on. 2. Ingest shared context (policies, docs, tickets, extracted file text) into a shared collection with stable `context_id`s. Sync SaaS tools with [connectors](/essentials/v2/connectors) instead of re-implementing them. 3. Ingest each user's conversations and stated preferences into their own collection (`collection = user_id`). -4. Link items that belong together (a ticket and its follow-ups, a policy and its FAQ) with `forceful_relations`. +4. Link contexts that belong together (a ticket and its follow-ups, a policy and its FAQ) with `forceful_relations`. 5. Poll `GET /context/status` or register a webhook. 6. Query with `collections: { "": 2, "company": 1 }`, `mode: "thinking"` for quality or `"fast"` for latency, and an `attributes` filter for hard scopes. 7. Inject `llm_prompt` into the model call with a grounding instruction, and let the model cite `[1]`, `[R1]`, `[P1]`. 8. Say so explicitly when the context does not contain the answer, and report it with `POST /feedback`. -Typical uses of that shape: a support agent that answers from policy while respecting each customer's stated preferences; workplace search over Slack, Notion and Drive with citations; an onboarding assistant over org charts, specs and meeting notes; an agent that records its own decisions as items and consults them before acting again. +Typical uses of that shape: a support agent that answers from policy while respecting each customer's stated preferences; workplace search over Slack, Notion and Drive with citations; an onboarding assistant over org charts, specs and meeting notes; an agent that records its own decisions as context and consults them before acting again. --- @@ -1514,16 +1514,16 @@ Method names are the same in both SDKs except where noted; Python takes snake_ca | `client.databases.collections()` | `GET /databases/collections` | List active collections | | `client.databases.stats()` | `GET /databases/stats` | Row counts | | `client.databases.update_metadata_schema()` (TS `updateMetadataSchema()`) | `PATCH /databases/{database}/metadata-schema` | Add declared attribute fields | -| `client.context.ingest()` | `POST /context/ingest` | Ingest text and conversation items | +| `client.context.ingest()` | `POST /context/ingest` | Ingest text and conversations as context | | `client.context.status()` | `GET /context/status` | Check indexing status | | `client.query()` | `POST /query` | Search; returns `chunks`, `graph`, `forceful_relations`, `llm_prompt` | | `client.feedback.submit()` | `POST /feedback` | Report how a query performed | -| `client.context.list()` | `POST /context/list` | List items | +| `client.context.list()` | `POST /context/list` | List context | | `client.context.inspect()` | `GET /context/inspect` | Read an item's stored content or a presigned URL | | `client.context.relations()` | `GET /context/relations` | Inspect graph relations | -| `client.context.subgraph()` | `GET /context/subgraph` | Walk an item's connected items | +| `client.context.subgraph()` | `GET /context/subgraph` | Walk the context connected to one context | | `client.context.update_source_metadata()` (TS `updateSourceMetadata()`) | `PATCH /context/{id}/metadata` | Merge new attribute values into an indexed item | -| `client.context.delete()` | `DELETE /context` | Delete items | +| `client.context.delete()` | `DELETE /context` | Delete context | Helpers: `verify_webhook_signature` (Python, `hydra_db.helpers`) and `verifyWebhookSignature` (TypeScript) verify webhook signatures. diff --git a/api-reference/v2/endpoint/create-tenant.mdx b/api-reference/v2/endpoint/create-tenant.mdx index d039cf74..d049bb80 100644 --- a/api-reference/v2/endpoint/create-tenant.mdx +++ b/api-reference/v2/endpoint/create-tenant.mdx @@ -1,12 +1,9 @@ --- title: "Create Database" -api: "POST https://api.hydradb.com/databases" -playground: "none" +openapi: "api-reference/v2/openapi.json POST /databases" description: "Creates a space for storing context. " --- -import { Field } from "/snippets/field.jsx"; - ```python Python SDK @@ -78,38 +75,20 @@ curl -X POST 'https://api.hydradb.com/databases' \ -## Request body - -`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. +`database` is the current field name (formerly `tenant_id`), and `database_metadata_schema` replaces `tenant_metadata_schema`. The old names remain accepted as deprecated aliases for full backward compatibility. -| Name | Description | -| --- | --- | -| | Database name, unique within your organization. Up to 255 characters, using only lowercase letters, digits, `-` and `_`. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Defines database-level metadata fields, the declared attributes you can filter on. Each entry is a schema field (below). Up to 32 fields, and at most 6 embedding flags in total: `enable_dense_embedding` and `enable_sparse_embedding` each count as one. See [Declare the schema](/essentials/v2/attributes#2-declare-the-schema) for detailed schema parameters. Formerly `tenant_metadata_schema`; the `tenant_metadata_schema` alias is still accepted (deprecated). (default=`null`) | - -### Schema field - -| Name | Description | -| --- | --- | -| | Field name. Must start with a letter, contain only letters, numbers and underscores, and not be a reserved system name. Immutable after creation. | -| | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the friendly aliases `string`, `integer`, `float`, `boolean`, `object`. `ARRAY` is rejected with `400`; for a multi-value field declare `VARCHAR` and store the values comma-joined. (default=`"VARCHAR"`) | -| | Maximum length for a `VARCHAR` field, up to `65535`. (default=`1024`) | -| | Enables exact-match filtering on this field. (default=`false`) | -| | Adds dense semantic search over a `VARCHAR` field. (default=`false`) | -| | Adds sparse (BM25) search over a `VARCHAR` field. (default=`false`) | - -## Successful response +Creation is asynchronous: the call returns `status: "accepted"` as soon as provisioning starts. Always check if a database is ready before using it. Use [Database Status](/api-reference/v2/endpoint/tenant-status) to check. -Creation is asynchronous: the call returns as soon as provisioning starts. Always check if a database is ready before using it. Use [Database Status](/api-reference/v2/endpoint/tenant-status) to check. +## Rules -| Name | Description | -| --- | --- | -| | `accepted`: provisioning has started in the background. | -| | The database being created. | -| | Human-readable result message. | -| | Deprecated alias for `database`, carrying the same value. | +- **`database`** is unique within your organization: up to 255 characters, using only lowercase letters, digits, `-` and `_`. +- **`database_metadata_schema`** declares the attributes you can filter on. Up to 32 fields, and at most 6 embedding flags in total: `enable_dense_embedding` and `enable_sparse_embedding` each count as one. See [Declare the schema](/essentials/v2/attributes#2-declare-the-schema). +- **Field `name`** must start with a letter, contain only letters, numbers and underscores, and not be a reserved system name. It is immutable after creation. +- **Field `data_type`** is `VARCHAR` (the default), `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the friendly aliases `string`, `integer`, `float`, `boolean`, `object`. `ARRAY` is rejected with `400`; for a multi-value field declare `VARCHAR` and store the values comma-joined. +- **Field `max_length`** applies to `VARCHAR` only: up to `65535`, default `1024`. +- **`enable_match`, `enable_dense_embedding` and `enable_sparse_embedding`** default to `false`. The two embedding flags apply to `VARCHAR` fields. diff --git a/api-reference/v2/endpoint/delete-collection.mdx b/api-reference/v2/endpoint/delete-collection.mdx index ad527c68..08524aa9 100644 --- a/api-reference/v2/endpoint/delete-collection.mdx +++ b/api-reference/v2/endpoint/delete-collection.mdx @@ -6,7 +6,7 @@ openapi: "api-reference/v2/openapi.json DELETE /databases/collections" import { Field } from "/snippets/field.jsx"; -This action is irreversible. Deleting a collection removes all context items, embeddings, and graph data stored under that collection. The parent database and its other collections are not affected. There is no soft-delete and no recovery window. +This action is irreversible. Deleting a collection removes all context, embeddings, and graph data stored under that collection. The parent database and its other collections are not affected. There is no soft-delete and no recovery window. ```python Python SDK @@ -99,7 +99,7 @@ Common codes: `400 INVALID_INPUT` (missing `database` or `collection`), `404 DAT **Related Resources** - **Before this:** [List Collections](/api-reference/v2/endpoint/list-sub-tenants): find the collection ID -- **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source): remove specific context items without deleting the collection +- **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source): remove specific context by ID without deleting the collection - **Larger scope:** [Delete Database](/api-reference/v2/endpoint/delete-tenant): remove the entire database - **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) diff --git a/api-reference/v2/endpoint/delete-source.mdx b/api-reference/v2/endpoint/delete-source.mdx index 445c36fb..cb14af3d 100644 --- a/api-reference/v2/endpoint/delete-source.mdx +++ b/api-reference/v2/endpoint/delete-source.mdx @@ -1,13 +1,10 @@ --- title: "Delete Context" -api: "DELETE https://api.hydradb.com/context" -playground: "none" -description: "Delete context items by their IDs." +openapi: "api-reference/v2/openapi.json DELETE /context" +description: "Delete context by ID." --- -import { Field } from "/snippets/field.jsx"; - -Pass one or more IDs in `ids` to delete those context items, whether you ingested them or a connector synced them. Send `database`, `collection`, and `ids` as top-level fields in the request body. Include the same `collection` you used when ingesting; omitting it targets the default collection. +Pass one or more IDs in `ids` to delete that context, whether you ingested it or a connector synced it. Send `database`, `collection`, and `ids` as top-level fields in the request body. Include the same `collection` you used when ingesting; omitting it targets the default collection. @@ -41,29 +38,6 @@ curl -X DELETE 'https://api.hydradb.com/context' \ -## Request body - -| Name | Description | -| --- | --- | -| | Database to delete from. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | -| | IDs of the context items to delete. At least one. | - -## Request headers - -| Name | Description | -| --- | --- | -| | Selects the status behaviour for this request. `strict` opts in to honest `404` / `409` / `500` codes when the delete did not happen; `legacy` forces the unconditional `200`. Omitted, the server default applies (currently `legacy`). See [Status codes](#status-codes). | - -## Response - -| Name | Description | -| --- | --- | -| | One entry per requested ID: `id`, `deleted` (whether this item was removed), and `error` (why not, present only when `deleted` is `false`). | -| | Number of items actually removed. `0` means nothing was deleted. | -| | Human-readable result message. | -| | Deprecated. Mirrors `deleted_count > 0`, so it is `false` for a delete that removed nothing even when the request itself returned `200`. Read `deleted_count` and `results[]` instead. | - ```json Success @@ -220,8 +194,9 @@ The header always wins. Without it, the server default applies. ## Some additional notes -- **Partial-success semantics:** Each ID is reported independently in `results[]`, and `deleted_count` totals the items actually removed. An ID that matched nothing comes back with `deleted: false` and an `error`, and does not stop the rest. One exception: if any item in the request is still indexing, the whole request is refused and nothing is deleted. That is reported as `409` in strict mode, and as a `200` with `deleted_count: 0` by default. -- **Retrieval drops the item immediately:** Even before background cleanup finishes, deleted IDs disappear from `/query` and `/context/list` responses. +- **Partial-success semantics:** Each ID is reported independently in `results[]`, and `deleted_count` totals the context actually removed. An ID that matched nothing comes back with `deleted: false` and an `error`, and does not stop the rest. One exception: if any ID in the request is still indexing, the whole request is refused and nothing is deleted. That is reported as `409` in strict mode, and as a `200` with `deleted_count: 0` by default. +- **`data.success` is deprecated:** it mirrors `deleted_count > 0`, so it is `false` for a delete that removed nothing even when the request itself returned `200`. Read `deleted_count` and `results[]` instead. +- **Retrieval drops deleted context immediately:** Even before background cleanup finishes, deleted IDs disappear from `/query` and `/context/list` responses.
diff --git a/api-reference/v2/endpoint/delete-tenant.mdx b/api-reference/v2/endpoint/delete-tenant.mdx index c2314a24..216d96de 100644 --- a/api-reference/v2/endpoint/delete-tenant.mdx +++ b/api-reference/v2/endpoint/delete-tenant.mdx @@ -6,7 +6,7 @@ openapi: "api-reference/v2/openapi.json DELETE /databases" import { Field } from "/snippets/field.jsx"; -This action is irreversible. Deleting a database removes all of its associated data, including all context items, embeddings, graph data, and the metadata schema. There is no soft-delete and no recovery window. +This action is irreversible. Deleting a database removes all of its associated data, including all context, embeddings, graph data, and the metadata schema. There is no soft-delete and no recovery window. The examples below use a placeholder name, `database_to_delete`. Replace it with the database you mean to destroy before running them, and check the name twice on a shared or team account. @@ -91,6 +91,6 @@ Common codes: `404 DATABASE_NOT_FOUND`, `401 UNAUTHORIZED`, `400 INVALID_INPUT` - **Before this:** [List Databases](/api-reference/v2/endpoint/list-tenants): find the database ID - **Alternative:** [Delete Collection](/api-reference/v2/endpoint/delete-collection): remove one collection without deleting the whole database -- **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source): remove specific context items without deleting the whole database +- **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source): remove specific context by ID without deleting the whole database - **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) diff --git a/api-reference/v2/endpoint/fetch-content.mdx b/api-reference/v2/endpoint/fetch-content.mdx index 42b604e4..bf64aa9b 100644 --- a/api-reference/v2/endpoint/fetch-content.mdx +++ b/api-reference/v2/endpoint/fetch-content.mdx @@ -188,8 +188,8 @@ These examples inspect an item ingested as text. **Text vs binary handling.** In `content` and `both` modes, `content` carries the stored item when it is valid UTF-8 text. When it is not, `content` is `null` and the bytes come back base64-encoded in `content_base64`. Check both fields when handling unknown content types. -- **Items ingested as text:** There is no separate original file. `content` is the text you sent (a `conversation` item is stored as JSON, so its `content` is a JSON document), `content_type` reports how it was stored, and in `url` and `both` modes `presigned_url` downloads that same stored item. -- **Recently ingested items:** Fetching immediately after ingestion may return a record before enrichment is ready. For reliable reads, use [Ingestion Status](/api-reference/v2/endpoint/source-status) first. +- **Context ingested as text:** There is no separate original file. `content` is the text you sent (a `conversation` is stored as JSON, so its `content` is a JSON document), `content_type` reports how it was stored, and in `url` and `both` modes `presigned_url` downloads that same stored item. +- **Recently ingested context:** Fetching immediately after ingestion may return a record before enrichment is ready. For reliable reads, use [Ingestion Status](/api-reference/v2/endpoint/source-status) first. - **Presigned URL TTL:** The URL is valid only for `expiry_seconds`. Anyone with the URL can download the item during that window, so treat it as a short-lived secret.
diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index b030a9d7..2ee9101a 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -1,13 +1,12 @@ --- title: "Ingest Context" -api: "POST https://api.hydradb.com/context/ingest" -playground: "none" -description: "Send text and conversations to a database as context items." +openapi: "api-reference/v2/openapi.json POST /context/ingest" +description: "Send text and conversations to a database as context." --- import { Field } from "/snippets/field.jsx"; -`POST /context/ingest` takes a list of **context items**, each a `text` or a `conversation`, and queues them for chunking, embedding, enrichment and graph extraction. The guide is [Ingest context](/essentials/v2/ingest); this page is the field reference. +`POST /context/ingest` takes a `context` list, where each context is a `text` or a `conversation`, and queues them for chunking, embedding, enrichment and graph extraction. The guide is [Ingest context](/essentials/v2/ingest); this page is the field reference. `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. @@ -18,7 +17,7 @@ import { Field } from "/snippets/field.jsx"; ```python Python SDK import json -# The SDK sends a multipart form; the item list goes in the `context` form field. +# The SDK sends a multipart form; the list goes in the `context` form field. result = client.context.ingest( database="acme_corp", collection="company", @@ -47,7 +46,7 @@ print([r.id for r in result.data.results]) ``` ```typescript TypeScript SDK -// The SDK sends a multipart form; the item list goes in the `context` form field. +// The SDK sends a multipart form; the list goes in the `context` form field. const result = await client.context.ingest({ database: "acme_corp", collection: "company", @@ -111,56 +110,50 @@ curl -X POST 'https://api.hydradb.com/context/ingest' \ ## Request body -Send `application/json`. The SDKs send `multipart/form-data` instead: the same array goes in the `context` form field as a JSON string, and the request-level fields are form fields of the same name (`graph_payload` also as a JSON string). Both entry points run the same validation. Keys inside each item stay snake_case in every language. +Send `application/json`, as in the cURL example. The SDKs send `multipart/form-data` instead, and that form is what the generated reference and the playground on this page show: the same array goes in the `context` form field as a JSON string, and the request-level fields are form fields of the same name (`graph_payload` also as a JSON string). Both entry points run the same validation. Keys inside each context stay snake_case in every language. -The SDK `ingest` methods take `database`, `collection`, `context`, `upsert`, `enrich`, `instructions` and `graph_payload`. On the form, `upsert` and `enrich` are strings: `"true"`, `"false"`, `"1"` or `"0"`; any other value is a `400`. `enrich` and `instructions` can also be set on each item. +The SDK `ingest` methods take `database`, `collection`, `context`, `upsert`, `enrich`, `instructions` and `graph_payload`. On the request, `upsert`, `enrich` and `instructions` are the defaults for every context (`true`, `true` and empty); each context can override them. On the form, `upsert` and `enrich` are strings: `"true"`, `"false"`, `"1"` or `"0"`; any other value is a `400`. -| Name | Description | -| --- | --- | -| | Target database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Collection inside the database. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=the default collection) | -| | The items to ingest, at least 1 and at most 100. In the multipart form the field is also `context`, a JSON-encoded array. | -| | Request-level default for every item's `enrich`. (default=`true`) | -| | Request-level default for every item's `upsert`. (default=`true`) | -| | Request-level default for every item's `instructions`. At most 4,000 characters after trimming. (default=empty) | -| | Bring Your Own Graph: a map of `context_id` to `{ entities, relations }` that replaces graph extraction for that item. Every key must match the `context_id` of an item in the same request, otherwise `400`. See [Bring Your Own Graph](#bring-your-own-graph) below. | + +The form also lists `type`, `documents`, `app_knowledge`, `memories`, `document_metadata`, `tenant_id` and `sub_tenant_id`. All are deprecated: send `context`, `database` and `collection` instead. + -### Item fields +### Context fields -Each item is exactly one of `text` or `conversation`. +Each entry in `context` is exactly one of `text` or `conversation`. | Name | Description | | --- | --- | -| | Your id for the item; the upsert key. Generated when omitted. At most 100 bytes. Must not contain a comma (`,`), which is the id separator on `/context/status?ids=`, and must not start with `att_` or `cmt_` (reserved for connector ids). | +| | Your id for the context; the upsert key. Generated when omitted. At most 100 bytes. Must not contain a comma (`,`), which is the id separator on `/context/status?ids=`, and must not start with `att_` or `cmt_` (reserved for connector ids). | | | Readable name. Searchable with `titles` on `/query`. Trimmed, then at most 1,024 bytes of UTF-8. | | | Plain text. Send exactly one of `text` or `conversation`. | -| | Turns of `{ role, content }`; roles are `user`, `assistant` and `system`. A turn takes no other key; the speaker is the item's `user_name`. `system` turns are never stored as facts: when neither the item nor the request sets `instructions`, they become the item's instructions and count toward the same 4,000-character limit; otherwise they are dropped. A conversation needs at least one `user` or `assistant` turn, and no turn may have empty `content`. | -| | Extract entities, relations and preferences from this item into the graph; the output is stored separately and returned as `enrichment` on query. (default=the request's `enrich`, else `true`) | -| | Replace an existing item with the same `context_id`, deleting its chunks and graph contribution first. (default=the request's `upsert`, else `true`) | -| | Steer enrichment for this item. At most 4,000 characters after trimming. (default=the request's `instructions`) | -| | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately and returns that as `received_at` on query chunks. | +| | Turns of `{ role, content }`; roles are `user`, `assistant` and `system`. A turn takes no other key; the speaker is the context's `user_name`. `system` turns are never stored as facts: when neither the context nor the request sets `instructions`, they become its instructions and count toward the same 4,000-character limit; otherwise they are dropped. A conversation needs at least one `user` or `assistant` turn, and no turn may have empty `content`. | +| | Extract entities, relations and preferences from this context into the graph; the output is stored separately and returned as `enrichment` on query. (default=the request's `enrich`, else `true`) | +| | Replace an existing context with the same `context_id`, deleting its chunks and graph contribution first. (default=the request's `upsert`, else `true`) | +| | Steer enrichment for this context. At most 4,000 characters after trimming. (default=the request's `instructions`) | +| | The date the context is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the context separately and returns that as `received_at` on query chunks. | | | Declared, filterable fields; keys must be in `database_metadata_schema`. Filter with `attributes` on `/query`. See [Attributes](/essentials/v2/attributes). | -| | Free-form fields. Stored with the item; not filterable and not returned on query chunks. | -| | Relations you declare to other items: `{ "context_ids": ["", ...], "properties": {} }`. Followed on `/query` in `thinking` mode with `follow_forceful_relations` and returned in `forceful_relations[]`. Each id follows the same rules as `context_id`. `properties` is optional and is stored on every edge the item declares: a flat map of string, number or boolean values, at most 1 KiB as compact JSON, with no empty key and none of the reserved keys `id`, `created_at`, `relation_type`, `tenant_id` or `sub_tenant_id`. | -| | Principals allowed to retrieve the item: bare emails or `user_email:`, `group:`, `domain:` principals, or `__public__`. Omit for unrestricted; `[]` for nobody. A malformed principal rejects the whole request with `400`. See [Access control](/essentials/v2/access-control). | -| | The speaker for the item: the author of a text item, or the person in a conversation's `user` turns. (default=`"User"`) | +| | Free-form fields. Stored with the context; not filterable and not returned on query chunks. | +| | Relations you declare to other contexts: `{ "context_ids": ["", ...], "properties": {} }`. Followed on `/query` in `thinking` mode with `follow_forceful_relations` and returned in `forceful_relations[]`. Each id follows the same rules as `context_id`. `properties` is optional and is stored on every edge the context declares: a flat map of string, number or boolean values, at most 1 KiB as compact JSON, with no empty key and none of the reserved keys `id`, `created_at`, `relation_type`, `tenant_id` or `sub_tenant_id`. | +| | Principals allowed to retrieve the context: bare emails or `user_email:`, `group:`, `domain:` principals, or `__public__`. Omit for unrestricted; `[]` for nobody. A malformed principal rejects the whole request with `400`. See [Access control](/essentials/v2/access-control). | +| | The speaker for the context: the author of a `text` context, or the person in a conversation's `user` turns. (default=`"User"`) | ### Limits - The whole request body is capped at **16 MiB**: the JSON body, or the `context` form field on the multipart form. A larger one is refused with `413` (`request body too large`). -- At most **100 items** per request, **1 MiB** of text per item, **8 MiB** of text per request. Text is an item's `text`, or the `content` of every turn of its `conversation`; titles and attributes are not counted. -- `attributes` at most **16 KiB** and `custom_attributes` at most **1 KiB** per item, measured on their compact JSON encoding. -- `title` at most **1,024 bytes**, and `instructions` at most **4,000 characters** on the request and on each item. A conversation's `system` turns are held to the same 4,000 characters when they become the item's instructions. -- The request is validated before anything is queued: one invalid item rejects the whole request with `400`, and the error names the item as `context[N]`. -- Unknown keys are refused. An unknown key at the top level of the body, on an item, on a conversation turn or inside `forceful_relations` is a `400` that names the key and lists the accepted ones. The same rule applies to the JSON in the `context` form field. +- At most **100 contexts** in `context` per request, **1 MiB** of text per context, **8 MiB** of text per request. Text is a context's `text`, or the `content` of every turn of its `conversation`; titles and attributes are not counted. +- `attributes` at most **16 KiB** and `custom_attributes` at most **1 KiB** per context, measured on their compact JSON encoding. +- `title` at most **1,024 bytes**, and `instructions` at most **4,000 characters** on the request and on each context. A conversation's `system` turns are held to the same 4,000 characters when they become the context's instructions. +- The request is validated before anything is queued: one invalid context rejects the whole request with `400`, and the error names it as `context[N]`. +- Unknown keys are refused. An unknown key at the top level of the body, on a context, on a conversation turn or inside `forceful_relations` is a `400` that names the key and lists the accepted ones. The same rule applies to the JSON in the `context` form field. ### Text only -Every item is text or a conversation. To ingest a file, extract its text and send it as a `text` item. Content from connected apps arrives through [connectors](/essentials/v2/connectors). +Every context is text or a conversation. To ingest a file, extract its text and send it as a `text` context. Content from connected apps arrives through [connectors](/essentials/v2/connectors). ## Bring Your Own Graph -`graph_payload` supplies the graph for an item yourself. HydraDB uses it instead of extracting a graph from that item; the item is still chunked and embedded, so it stays searchable. Each top-level key is the `context_id` of an item in the same request, so give keyed items an explicit `context_id`. See [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) for the full guide. +`graph_payload` supplies the graph for a context yourself. HydraDB uses it instead of extracting a graph from that context; the context is still chunked and embedded, so it stays searchable. Each top-level key is the `context_id` of a context in the same request, so give those contexts an explicit `context_id`. See [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) for the full guide. @@ -252,7 +245,7 @@ await client.context.ingest({ | Field | Description | | --- | --- | -| | Top-level key: the `context_id` of an item in this request. Value is that item's graph. A key matching no item returns `400`. | +| | Top-level key: the `context_id` of a context in this request. Value is that context's graph. A key matching no context returns `400`. | | | Non-empty map keyed by a caller-local handle (at most 256 characters) that `relations` reference; the handle is not stored. | | | Entity name. At most 256 characters. | | | Entity type (e.g. `PERSON`, `POLICY`). Stored as supplied. At most 256 characters. | @@ -271,7 +264,7 @@ Caps per graph: at most 5,000 entities, 10,000 relations and 500 relations per e ## Response -`202 Accepted`, with one result per item: +`202 Accepted`, with one result per context: ```json { @@ -291,18 +284,10 @@ Caps per graph: at most 5,000 entities, 10,000 relations and 500 relations per e } ``` -| Field | Description | -| --- | --- | -| `message` | `Context queued for ingestion successfully` (`Context ingestion completed with some failures` when an item failed), followed by a reminder to poll status. | -| `results[].id` | The item's `context_id`, sent or generated. Pass it to [`GET /context/status`](/api-reference/v2/endpoint/source-status). | -| `results[].title` | The item's `title`, or `null`. | -| `results[].status` | `queued` or `failed`. A failed item does not stop the others. | -| `results[].infer` | Mirrors the item's `enrich`. | -| `results[].error`, `results[].error_code` | Why the item failed; `null` on success. | -| `success_count`, `failed_count` | Totals across `results`. | +Each entry in `results` reports one context, in request order: `id` is its `context_id` (sent or generated), which you pass to [`GET /context/status`](/api-reference/v2/endpoint/source-status); `status` is `queued` or `failed`, and a failed entry does not stop the others; `error` and `error_code` say why it failed and are `null` on success. `success_count` and `failed_count` total the entries. -**`202 Accepted` means queued, not indexed.** Ingestion is asynchronous. Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) with the returned ids until each item reaches `completed` or `errored` (`graph_creation` is already searchable), or register a webhook for `indexing.status_changed` events (see [Webhooks](/essentials/v2/webhooks)). +**`202 Accepted` means queued, not indexed.** Ingestion is asynchronous. Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) with the returned ids until each context reaches `completed` or `errored` (`graph_creation` is already searchable), or register a webhook for `indexing.status_changed` events (see [Webhooks](/essentials/v2/webhooks)).
@@ -312,7 +297,7 @@ Caps per graph: at most 5,000 entities, 10,000 relations and 500 relations per e - **Always check** [ingestion status](/api-reference/v2/endpoint/source-status) to ensure context is ready to be retrieved - [Query](/api-reference/v2/endpoint/query) once context is ready - - **Browse:** [List Context](/api-reference/v2/endpoint/list-documents) lists the items you ingested with their titles and attributes; send `ids` to look up specific ones + - **Browse:** [List Context](/api-reference/v2/endpoint/list-documents) lists the context you ingested with titles and attributes; send `ids` to look up specific ones - **Inspect:** [Inspect Context](/api-reference/v2/endpoint/fetch-content) returns the stored content behind a `context_id` - **Cleanup:** [Delete Context](/api-reference/v2/endpoint/delete-source) - **Collections:** omitting `collection` writes to the default collection; list them with [List Collections](/api-reference/v2/endpoint/list-sub-tenants) diff --git a/api-reference/v2/endpoint/list-documents.mdx b/api-reference/v2/endpoint/list-documents.mdx index 77a4c7d9..ba531c04 100644 --- a/api-reference/v2/endpoint/list-documents.mdx +++ b/api-reference/v2/endpoint/list-documents.mdx @@ -1,20 +1,19 @@ --- title: "List Context" -api: "POST https://api.hydradb.com/context/list" -playground: "none" -description: "Browse the context items in a database or collection with optional filters. Results are paginated. " +openapi: "api-reference/v2/openapi.json POST /context/list" +description: "Browse the context in a database or collection with optional filters. Results are paginated." --- import { Field } from "/snippets/field.jsx"; -List the context items in a database or collection: everything you ingested and everything your connectors synced, in one paginated listing. Each row carries an item's `id` and its metadata; fetch the full content of one item with [Inspect Context](/api-reference/v2/endpoint/fetch-content). +List the context in a database or collection: everything you ingested and everything your connectors synced, in one paginated listing. Each row carries a context's `id` and its metadata; fetch the full content of one with [Inspect Context](/api-reference/v2/endpoint/fetch-content). Supports pagination, metadata filters, and field projection. For metadata design and query-time behavior, see [Attributes](/essentials/v2/attributes). ```python Python SDK -items = client.context.list( +page = client.context.list( database="acme_corp", page=1, page_size=50, @@ -27,7 +26,7 @@ items = client.context.list( ``` ```typescript TypeScript SDK -const items = await client.context.list({ +const page = await client.context.list({ database: "acme_corp", page: 1, pageSize: 50, @@ -58,25 +57,18 @@ curl -X POST 'https://api.hydradb.com/context/list' \ -## Request body +## Request notes -| Name | Description | -| --- | --- | -| | Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | -| | When provided and non-empty, only items with these IDs are returned (pagination \+ filters still apply). At most `100` IDs. (default=`null`) | -| | Page number (1-indexed). (default=`1`) | -| | Items per page, from `1` to `100`. (default=`50`) | -| | Structured filters. See [Filters](#1-filters). (default=`null`) | -| | Field projection. Only the listed fields plus `id`, `database`, `collection` are populated. See [Including fields](#2-including-fields-for-convenient-data-objects). (default=`null`, meaning all fields) | -| | Nest each thread's comments and replies under their parent row as `comments`, newest first, instead of listing them as separate rows. (default=`false`) | -| | Principals to answer as: only items they may see are listed. Omit for no access scoping. See [Access control](/essentials/v2/access-control). | +- **Pagination:** `page` is 1-indexed (default `1`); `page_size` runs from `1` to `100` (default `50`). +- **`ids`:** when non-empty, only those IDs are listed, at most `100`. Pagination and `filters` still apply. +- **`group_threads`:** nests each thread's comments and replies under their parent row as `comments`, newest first, instead of listing them as separate rows (default `false`). +- **`acl`:** principals to answer as; only context they may see is listed. Omit for no access scoping. See [Access control](/essentials/v2/access-control). ### 1. Filters - `filters` is a structured object with three optional categories. Each filter is an exact match against the stored value. The one exception is `source_fields.title`, which matches as a case-insensitive prefix. There are no range, contains, or OR operators on this endpoint. A `null` filter value returns `400`. - **AND/OR:** All filter pairs combine with a logical AND. To express OR semantics, run multiple calls and union them client-side. -- **`ids` + filters:** When `ids` is non-empty, only those IDs are considered, and the other `filters` still apply on top. For example, list items 1, 2 and 3 only if they also have `department=legal`. +- **`ids` + filters:** When `ids` is non-empty, only those IDs are considered, and the other `filters` still apply on top. For example, list IDs 1, 2 and 3 only if they also have `department=legal`. ```json { @@ -90,9 +82,9 @@ curl -X POST 'https://api.hydradb.com/context/list' \ | Category | Matched against | Notes | | --- | --- | --- | -| | Context item's schema-aligned `metadata` payload | Use for database metadata fields. `tenant_metadata` is accepted as a legacy alias. Each key is matched against the item's stored value; no `enable_match` declaration is needed on this endpoint. | -| | Context item's `additional_metadata` payload | Free-form per-item JSON. No schema declaration required. `document_metadata` is accepted as a legacy alias. | -| | Built-in item fields: `type`, `title`, `description`, `url`, `timestamp`, and the connector fields `app_provider`, `app_kind`, `app_external_id`, `app_parent_id` | Use for connector categories or quick title lookups. Any other key returns `400`. `app_external_id` and `app_parent_id` are only unique per provider, so pair them with `app_provider`. | +| | The context's schema-aligned `metadata` payload | Use for database metadata fields. `tenant_metadata` is accepted as a legacy alias. Each key is matched against the stored value; no `enable_match` declaration is needed on this endpoint. | +| | The context's `additional_metadata` payload | Free-form JSON per context. No schema declaration required. `document_metadata` is accepted as a legacy alias. | +| | Built-in fields: `type`, `title`, `description`, `url`, `timestamp`, and the connector fields `app_provider`, `app_kind`, `app_external_id`, `app_parent_id` | Use for connector categories or quick title lookups. Any other key returns `400`. `app_external_id` and `app_parent_id` are only unique per provider, so pair them with `app_provider`. | ### 2. Including Fields for convenient data objects @@ -101,17 +93,17 @@ When you don't need every field on every row, pass `include_fields` to keep resp Allowed values are `title`, `type`, `description`, `note`, `timestamp`, `metadata`, `additional_metadata`, and `relations`. Omit or pass `null` to return everything. - **Projectable vs. fetchable fields.** `content`, `url`, and `attachments` are **not** valid `include_fields` values: they are stripped from list responses, and requesting one returns `400`. Fetch them per item via [Inspect Context](/api-reference/v2/endpoint/fetch-content). + **Projectable vs. fetchable fields.** `content`, `url`, and `attachments` are **not** valid `include_fields` values: they are stripped from list responses, and requesting one returns `400`. Fetch them per context via [Inspect Context](/api-reference/v2/endpoint/fetch-content). ## Response -`data` holds one page of the listing: +`data` holds one page of the listing. The generated schema on this page also lists `user_memories` and `memory_id`: those only appear when a request sends the deprecated `type: "memory"`. Without `type`, every row is in `sources`. | Name | Description | | --- | --- | -| | The listed context items, one row per item (fields below). | -| | Total number of matching items across all pages. | +| | The listed context, one row per context (fields below). | +| | Total number of matching rows across all pages. | | | `page`, `page_size`, `total`, `total_pages`, `has_next`, `has_previous`. | | | Human-readable result message. | | | Deprecated. Always the same value as the envelope's top-level `success`; check the HTTP status instead. | @@ -120,24 +112,24 @@ Each row in `sources` carries: | Name | Description | | --- | --- | -| | The item's ID. Always present. | -| | Database the item was listed from. Always present. | -| | Collection the item was listed from. Empty when it is in the database's default collection. Always present. | -| | Title of the item. | -| | Source kind of the item. | +| | The context's ID. Always present. | +| | Database the row was listed from. Always present. | +| | Collection the row was listed from. Empty when it is in the database's default collection. Always present. | +| | Title of the context. | +| | Source kind of the row. | | | Human-readable description. | -| | Free-form note attached to the item. | -| | RFC3339 timestamp associated with the item. | +| | Free-form note attached to the context. | +| | RFC3339 timestamp associated with the context. | | | Database metadata (declared fields) supplied at ingest. | | | Free-form metadata supplied at ingest or by a connector. | -| | Relations attached to the item. Returned only when requested with `include_fields`. | -| | Connector the item came from (for example `slack` or `github`). Absent for items that did not come from a connector. | -| | Connector item category. | -| | Provider-assigned identifier for the item. | -| | Provider ID of the item's parent in a conversation (for example a Jira comment's issue key, or a Slack reply's thread root). | +| | Relations attached to the context. Returned only when requested with `include_fields`. | +| | Connector the context came from (for example `slack` or `github`). Absent for context that did not come from a connector. | +| | Connector object category. | +| | Provider-assigned identifier. | +| | Provider ID of the parent in a conversation (for example a Jira comment's issue key, or a Slack reply's thread root). | | | Discussion grouping key shared by a thread root and its replies or comments. | -| | Connector-derived relations for the item. | -| | With `group_threads`, the item's comments and replies as full rows, newest first, capped per parent. | +| | Connector-derived relations. | +| | With `group_threads`, the row's comments and replies as full rows, newest first, capped per parent. | | | With `group_threads`, `true` when `comments` hit the per-parent cap and more exist. | | | Deprecated alias for `database`. | | | Deprecated alias for `collection`. | diff --git a/api-reference/v2/endpoint/list-tenants.mdx b/api-reference/v2/endpoint/list-tenants.mdx index c8c69940..d6ccbe4a 100644 --- a/api-reference/v2/endpoint/list-tenants.mdx +++ b/api-reference/v2/endpoint/list-tenants.mdx @@ -1,12 +1,9 @@ --- title: "List Databases" -api: "GET https://api.hydradb.com/databases" -playground: "none" +openapi: "api-reference/v2/openapi.json GET /databases" description: "List all databases created. " --- -import { Field } from "/snippets/field.jsx"; - The response separates active or provisioning databases (in `data.databases`) from databases whose provisioning failed (in `data.failed_databases`). Use [Database Status](/api-reference/v2/endpoint/tenant-status) to confirm readiness before ingestion. This endpoint takes no parameters. @@ -83,17 +80,6 @@ curl -X GET 'https://api.hydradb.com/databases' \ -## Response - -| Name | Description | -| --- | --- | -| | Active or provisioning databases in your organization. | -| | One entry per database in `databases`, each carrying its `database` name. | -| | Databases whose provisioning failed. Each entry has `database` and `error` (why provisioning failed). Empty when none failed. | -| | Human-readable result message. | -| | Deprecated alias for `databases`. | -| | Deprecated alias for `failed_databases`; `null` when none failed. | - ## Retry notes - If provisioning failed for a database, `data.failed_databases` contains diagnostic entries as shown in the **Provisioning issue** tab. diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index 2d5d41b9..7c9067c8 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -41,7 +41,7 @@ linkStyle default stroke:#64748b,stroke-width:2px; | | `0.0` to `1.0` or `"auto"` | Tune hybrid query. Lower values favor BM25 keywords; higher values favor semantic similarity. Defaults to `0.8`; `"auto"` also resolves to `0.8`. | | | object | Narrow candidates with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on the fields declared in `database_metadata_schema`. | | | boolean | Include graph paths in `graph[]`. On by default; set `false` for chunk-only responses. | -| | boolean | Pull items linked with `forceful_relations` at ingest into `forceful_relations[]`. On by default; followed only in `thinking` mode. | +| | boolean | Pull context linked with `forceful_relations` at ingest into `forceful_relations[]`. On by default; followed only in `thinking` mode. | | | boolean | Adds app-aware retrieval for connector content while still querying the full selected scope. On by default; set `false` to skip it. | diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index c70ca340..34d2a542 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -1,12 +1,9 @@ --- title: "Query" -api: "POST https://api.hydradb.com/query" -playground: "none" +openapi: "api-reference/v2/openapi.json POST /query" description: "Retrieve ranked chunks, graph paths, forceful relations and a prompt-ready string from a database in one call." --- -import { Field } from "/snippets/field.jsx"; - The single retrieval endpoint. Use it any time you need to feed an LLM with grounded, personalized context, or fetch chunks ranked by relevance. Two dimensions control behavior: @@ -39,7 +36,7 @@ result = client.query( recency_bias=0.2, graph_context=True, - # Pull in the items each hit declared with forceful_relations at ingest. + # Pull in the context each hit declared with forceful_relations at ingest. follow_forceful_relations=True, # Hard filter on declared attributes. @@ -66,7 +63,7 @@ const result = await client.query({ recencyBias: 0.2, graphContext: true, - // Pull in the items each hit declared with forceful_relations at ingest. + // Pull in the context each hit declared with forceful_relations at ingest. followForcefulRelations: true, // Hard filter on declared attributes. @@ -337,31 +334,23 @@ result = client.query( -## Request body +## Defaults and limits -| Name | Description | -| --- | --- | -| | Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Single collection scope. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=default collection) | -| | Multi-collection scope. Send a list of collection IDs for equal weighting, or an object mapping collection ID to a positive relative weight (at most one decimal place, e.g. `{"user_alex": 2, "company": 1}`) to bias ranking. Up to 100 collections. Do not combine with `collection`. Formerly `sub_tenant_ids` (deprecated). | -| | Query terms or natural-language question. Cannot be empty. | -| | Retrieval method. See [Query methods](#decision-matrix). (default=`"hybrid"`) | -| | BM25 operator for `query_by: "text"`. Ignored for `hybrid`. (default=`"or"`) | -| | Retrieval pipeline. Applies to `hybrid` only; ignored for `text`. `"auto"` scores the query and resolves it to `"fast"` or `"thinking"`, defaulting to `"thinking"` when the signal is inconclusive. (default=`"auto"`) | -| | Maximum chunks to return. Default `10`; maximum `250`. Start with `10`, use `5` for tight prompts, and increase only when reranking downstream. | -| | Hybrid weight (`1.0` = pure semantic, `0.0` = pure BM25). Applies to `query_by: "hybrid"` only. `"auto"` resolves to the default. (default=`0.8`) | -| | Boost newer content. Send `0` to disable recency entirely. (default=`0.4`) | -| | Restrict retrieval to these `context_id`s, at most 200. A scoped search that matches nothing returns nothing. | -| | Restrict retrieval to items with one of these exact titles (case-insensitive, ORed), at most 500. Intersected with `ids` when both are sent. | -| | Adds an app-aware retrieval step for connector content (exact IDs, actors, thread and parent traversal) while still querying the full selected scope. Set `false` to skip it. (default=`true`) | -| | Principals to answer as: only items they may retrieve are returned. Omit, or send `[]` or `["*"]`, for no access scoping. See [Access control](/essentials/v2/access-control). | -| | Hard filter on declared attributes with operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$exists`. Applies to chunks, forceful relations and graph paths alike. See [Filters](#decision-matrix). | -| | When `true`, includes graph paths in `graph[]`. Set to `false` when you only need ranked chunks; `graph` is then `[]`. Relations you supplied via [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) appear identically to extracted ones. (default=`true`) | -| | Pull the items each hit declared with `forceful_relations` at ingest into `forceful_relations[]`. Declared relations are followed only in `thinking` mode. Set to `false` for `forceful_relations: []`. `query_forceful_relations` is the deprecated alias. (default=`true`) | -| | Resolve time-based questions (current, as of, ranges, upcoming) and return matched facts in `chunks[].temporal`. Never changes which chunks are returned. (default=`true`) | -| | ISO 8601 time to treat as now for temporal reasoning. Set it when replaying past conversations. | -| | Override the temporal intent HydraDB would infer from the query. | -| | Deprecated. The older filter language, ANDed with `attributes` when both are sent. Prefer `attributes`; use `metadata_filters` only to filter on connector fields under `additional_metadata`, which `attributes` does not reach (see [Connectors](/essentials/v2/connectors)). | +The generated reference below lists every request field. What to know when you leave a field out: + +| Field | Default | Limit | +| --- | --- | --- | +| `collection` / `collections` | the default collection | `collections`: up to 100; do not combine with `collection` | +| `query_by` | `"hybrid"` | `"text"` pairs with `operator` (default `"or"`) | +| `mode` | `"auto"` | applies to `hybrid` only | +| `max_results` | `10` | `250` | +| `alpha` | `0.8` (`"auto"` resolves to it) | `0.0` to `1.0`; `hybrid` only | +| `recency_bias` | `0.4` | `0.0` to `1.0`; `0` disables recency | +| `ids` | whole scope | 200 `context_id`s | +| `titles` | whole scope | 500 exact titles, case-insensitive | +| `graph_context`, `follow_forceful_relations`, `temporal_reasoning`, `query_apps` | `true` | | + +`attributes` is the filter to use; `metadata_filters` is deprecated and ANDed with it when both are sent (use it only for connector fields under `additional_metadata`, see [Connectors](/essentials/v2/connectors)). `acl` answers as the given principals; omit it, or send `[]` or `["*"]`, for no access scoping (see [Access control](/essentials/v2/access-control)). **Tuning heuristics.** @@ -544,11 +533,17 @@ result = client.query( +## Response + + +The generated response schema on this page is a union of two bodies: the older v2 body (`chunks` with `chunk_content`, `graph_context`, `sources` and more) and the four-key body below. Read the four-key body; it is what the examples on this page show. + + `data` is exactly four keys. A query that matches nothing returns `200` with empty `chunks`, `graph` and `forceful_relations` and an empty `llm_prompt` rather than an error. | Key | Contents | | --- | --- | -| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content` (verbatim), `enrichment` (the extracted statement as a plain string, omitted when there is none), `enrichment_kind` (an optional label; omitted when none was set), `received_at` (when HydraDB received the item, RFC 3339; this is not the item's `happened_at`, and it is omitted when no receipt time is recorded, as on older items), `temporal[]` (only when the query engaged temporal reasoning; `{ content, start_date, end_date }`, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`). | +| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content` (verbatim), `enrichment` (the extracted statement as a plain string, omitted when there is none), `enrichment_kind` (an optional label; omitted when none was set), `received_at` (when HydraDB received the context, RFC 3339; this is not its `happened_at`, and it is omitted when no receipt time is recorded, as on context ingested before it existed), `temporal[]` (only when the query engaged temporal reasoning; `{ content, start_date, end_date }`, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`). | | `graph[]` | Paths through the context graph, query paths first then chunk expansions: `origin`, `triplets[]` of `source` / `relation` / `target`, plus `path_summary`. `origin` is `"query_path"` (grown from the entities in the query) or `"chunk_relation"` (the neighbourhood of a returned chunk, only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk). The array is deduplicated across both origins and is not capped. `path_summary` is never empty: when the server wrote no summary, it narrates the hops. Entities are `{ entity_id, name }`; relations are `{ predicate, context, temporal_details?, timestamp?, relationship_id, chunk_id }`, where `temporal_details` is omitted when empty and `timestamp` (Unix epoch seconds, a float) is omitted when the edge has none. `[]` when `graph_context` is `false`. | | `forceful_relations[]` | Chunks pulled in through `forceful_relations` declared at ingest, followed only in `thinking` mode: `via.from` (the context whose declaration pulled it in, may be `""`), `via.to` (the chunk's own `context_id`), `chunk` (same shape as `chunks[]`). `[]` when none, when `follow_forceful_relations` is `false`, or when the query ran in `fast` mode. | | `llm_prompt` | A server-built markdown string ready to inject into a model call: `# Query results`, then `## Results`, `## Forceful relations`, `## Related facts`, `## Temporal facts` (with a `**Duration:**` line for a "how long between" question), `## Source facts`, `## Profiles`, `## Code search` and `## Sources`, each left out when empty. Source facts, profiles, code-search answers and the duration are prompt only: no JSON key carries them. Results are cited `[1]` and forceful relations `[R1]`; related facts are labelled `[P1]`, `[P2]`, ... in `graph[]` order, as in `- [P1] **Refunds** -managed_by→ **Finance** (relevance 0.81) [1]`: the parenthetical is the path's relevance after reranking and is left out when the path has none, and the line ends with the results the path was extracted from. Sources print only web (`http` or `https`) links. `""` only when the query found nothing at all. The layout is on [Query](/essentials/v2/query#llm_prompt). | @@ -558,7 +553,7 @@ To show a chunk's graph paths under that chunk, group hops by `triplets[].relati `meta` carries `request_id`, `api_version`, `latency_ms`, `database` and `collection`, plus a `deprecation` list when the request used a deprecated name. `collection` is present when the query searched one collection (named, or the default); a `collections` fan-out omits it. -**Chunks carry almost no source details.** A chunk has no title, url, collection or attributes; the one source detail it carries is `received_at`. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show an item's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. +**Chunks carry almost no source details.** A chunk has no title, url, collection or attributes; the one source detail it carries is `received_at`. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show a context's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. ## Behavior notes @@ -573,7 +568,7 @@ To show a chunk's graph paths under that chunk, group hops by `triplets[].relati **Important Considerations & Common Mistakes** - **Filter with `attributes`, on declared fields.** On a database with a `database_metadata_schema`, a key that is not declared in it is a `400`; `custom_attributes` are not filterable with `attributes`. -- **Common mistakes.** Check [Ingestion Status](/api-reference/v2/endpoint/source-status) for recently ingested items before querying. If you omit `collection` and `collections`, HydraDB queries the default collection; use [List Collections](/api-reference/v2/endpoint/list-sub-tenants) to discover available IDs. +- **Common mistakes.** Check [Ingestion Status](/api-reference/v2/endpoint/source-status) for recently ingested context before querying. If you omit `collection` and `collections`, HydraDB queries the default collection; use [List Collections](/api-reference/v2/endpoint/list-sub-tenants) to discover available IDs. ## Errors @@ -589,7 +584,7 @@ Common codes: `400 INVALID_INPUT` (empty `query`), `400 VALIDATION_ERROR` (a mal - **Setup first:** [Ingest Context](/api-reference/v2/endpoint/ingest-context): content must be indexed - **Confirm indexing:** [Ingestion Status](/api-reference/v2/endpoint/source-status): wait for `completed` (or `graph_creation`) -- **Source details:** [List Context](/api-reference/v2/endpoint/list-documents) with `ids` for an item's title and attributes, [Inspect Context](/api-reference/v2/endpoint/fetch-content) for its stored content +- **Source details:** [List Context](/api-reference/v2/endpoint/list-documents) with `ids` for a context's title and attributes, [Inspect Context](/api-reference/v2/endpoint/fetch-content) for its stored content - **Graph follow-up:** [Context Relations](/api-reference/v2/endpoint/source-relations): inspect relationships in detail - **Concepts:** [Usage: Query](/essentials/v2/query) - **Concepts:** [Concepts: Context Graphs](/essentials/v2/context-graphs) diff --git a/api-reference/v2/endpoint/source-relations.mdx b/api-reference/v2/endpoint/source-relations.mdx index a835a889..552243cc 100644 --- a/api-reference/v2/endpoint/source-relations.mdx +++ b/api-reference/v2/endpoint/source-relations.mdx @@ -1,15 +1,12 @@ --- title: "Inspecting Context Relations" -api: "GET https://api.hydradb.com/context/relations" -playground: "none" +openapi: "api-reference/v2/openapi.json GET /context/relations" description: "See and explore relationships that create the brain for your AI. " --- -import { Field } from "/snippets/field.jsx"; - This endpoint queries entity-and-relationship triplets extracted from your ingested content. -Pass `id` to scope to a single ingested item, or omit it to return all relations in the collection. Pagination handles large result sets. +Pass `id` to scope to a single ingested context, or omit it to return all relations in the collection. Pagination handles large result sets. @@ -40,44 +37,6 @@ curl -G 'https://api.hydradb.com/context/relations' \ -## Query parameters - -| Name | Description | -| --- | --- | -| | Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | When provided, returns relations for that specific source. When omitted, returns all relations across the collection. (default=`null`) | -| | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | -| | Maximum relation groups to return. Range `1` to `10000`. (default=`5000`) | -| | Opaque pagination cursor from a previous response's `next_cursor`. (default=`null`) | -| | Principals to answer as: only relations from items they may see are returned. Repeated (`acl=a&acl=b`) or comma-separated. Omit for no ACL scoping. | - -## Response - -| Name | Description | -| --- | --- | -| | Entity relations, grouped per entity pair. Each group has `source` and `target` entities, the `relations[]` evidence between them, and the `chunk_id` the group was found in. Counted against `limit` and paged with `cursor`. | -| | The structural graph around those relations: where entities appear, comments and attachments on items, who authored what, and links between items. Same shape as `relations`, so concatenate the two for one graph. Does not count against `limit` or move the cursor. | -| | `true` when `auxiliary_relations` was cut off by a size limit. Independent of `is_truncated`. | -| | `true` when more `relations` exist beyond this page. | -| | Cursor for the next page, or `null` when there are no more. | -| | Human-readable result message. | -| | Deprecated. Always the same value as the envelope's top-level `success`. | - -Each entity (`source`, `target`) carries `name`, `type`, `namespace`, `entity_id`, `identifier` (`null` when none) and `provider` (the connector its evidence came from, empty when none). Each entry in a group's `relations[]` carries: - -| Name | Description | -| --- | --- | -| | Normalized relation name. | -| | The relation as extracted from the text. | -| | The sentence or passage the relation was extracted from. | -| | Extraction confidence. | -| | When the relation held, if the text said. | -| | When the relation was recorded, as an ISO-8601 string. | -| | Stable ID of the relation. | -| | Chunk the relation was extracted from. | -| | Entity ID of the relation's source end. | -| | Entity ID of the relation's target end. | - ```json Success @@ -230,9 +189,11 @@ while True: **Cursor opacity.** `next_cursor` is opaque (currently a numeric score). Don't construct it client-side or assume meaning; pass back exactly what the server returned. +- **`limit`** ranges from `1` to `10000` relation groups (default `5000`). +- **`relations` and `auxiliary_relations`:** `relations` holds the entity relations, grouped per entity pair, and is what `limit` and `cursor` page through. `auxiliary_relations` is the structural graph around them (where entities appear, comments and attachments, who authored what, links between contexts) in the same shape, so concatenate the two for one graph. It does not count against `limit` or move the cursor; `auxiliary_truncated` reports when a size limit cut it off, independently of `is_truncated`. - **Collection-wide queries:** Omitting `id` returns relations across the entire collection. This is useful for full-graph exports; pair with a small `limit` and paginate. - **Ordering:** Treat `data.relations[]` as ranked by relevance within the response. Preserve order for display or LLM context, but do not compare ordering across unrelated queries as an absolute signal. -- **Graph completeness:** Source relations only fully populate once the source's `indexing_status` reaches `completed`. Items in `graph_creation` are searchable but their relations may still be in flight. +- **Graph completeness:** Source relations only fully populate once the source's `indexing_status` reaches `completed`. Context in `graph_creation` is searchable but their relations may still be in flight. - **`timestamp` format differs by endpoint.** On this endpoint each relation's `timestamp` is an ISO-8601 string (e.g. `2026-05-12T08:14:00Z`). On [Query](/api-reference/v2/endpoint/query), each `graph[].triplets[].relation` may carry `timestamp` as Unix epoch seconds (a float) instead. Normalize before comparing relation timestamps across the two endpoints.
diff --git a/api-reference/v2/endpoint/source-status.mdx b/api-reference/v2/endpoint/source-status.mdx index 78d41e25..77da4354 100644 --- a/api-reference/v2/endpoint/source-status.mdx +++ b/api-reference/v2/endpoint/source-status.mdx @@ -47,8 +47,8 @@ curl -G 'https://api.hydradb.com/context/status' \ | Name | Description | | --- | --- | -| | One or more `id` values returned at ingestion. Accepts the ID of any context item, including connector items. Pass either repeated params (`ids=a&ids=b`) or a single comma-joined value (`ids=a,b`); the two forms can be mixed. IDs never contain commas (they are rejected at ingest), so the comma-joined form always splits unambiguously. Surrounding whitespace is trimmed, and empty and duplicate entries are dropped. | -| | Database the items belong to. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | +| | One or more `id` values returned at ingestion. Accepts the ID of any context, including context synced by a connector. Pass either repeated params (`ids=a&ids=b`) or a single comma-joined value (`ids=a,b`); the two forms can be mixed. IDs never contain commas (they are rejected at ingest), so the comma-joined form always splits unambiguously. Surrounding whitespace is trimmed, and empty and duplicate entries are dropped. | +| | Database the context belongs to. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | | | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | @@ -119,7 +119,7 @@ Each entry in `data.statuses` describes one requested `id`: | `indexing_status` | string | One of the [status values](#status-values) below. `errored` is terminal. | | `error_code` | string | Machine-readable reason an entry is `errored`; **empty string (`""`) when the entry is not errored.** See [`error_code` values](#error-code-values). | | `error_message` | string | Human-readable explanation of an ingestion-pipeline `error_code`. Empty otherwise, including for `FILE_NOT_FOUND`. | -| `success` | boolean | `false` when `indexing_status` is `errored`, otherwise `true`. Describes the item, **not** the HTTP request: a `200` response can contain `errored` items. | +| `success` | boolean | `false` when `indexing_status` is `errored`, otherwise `true`. Describes the context, **not** the HTTP request: a `200` response can contain `errored` entries. | | `message` | string | Status of the *lookup* itself: "Processing status retrieved successfully", or "ID not found" for an unknown `id`. It does **not** describe the ingestion outcome; read `indexing_status` and `error_code` for that. | ### Error code values @@ -246,14 +246,14 @@ while True: Typical processing time: -- **Short text and conversation items:** seconds -- **Long text items** (the extracted text of a document under 50 pages): 1 to 5 minutes -- **Very long text items** (50\+ pages of extracted text): 5 to 15 minutes +- **Short text and conversations:** seconds +- **Long text** (the extracted text of a document under 50 pages): 1 to 5 minutes +- **Very long text** (50\+ pages of extracted text): 5 to 15 minutes ## Behavior notes - **`graph_creation` is searchable.** Items in this state are already retrievable via `/query`. Wait for `completed` only when you specifically need full graph traversal (graph paths in `graph[]`, which the `graph_context` request flag turns on). + **`graph_creation` is searchable.** Context in this state are already retrievable via `/query`. Wait for `completed` only when you specifically need full graph traversal (graph paths in `graph[]`, which the `graph_context` request flag turns on). - **Unknown IDs return as `errored`:** If you pass an ID that does not exist (e.g., a typo), HydraDB returns an entry with `indexing_status: "errored"` and `error_code: "FILE_NOT_FOUND"` rather than silently dropping it. Use `error_code` to distinguish this from a genuine ingestion failure; see [`error_code` values](#error-code-values). diff --git a/api-reference/v2/endpoint/sources-overview.mdx b/api-reference/v2/endpoint/sources-overview.mdx index 43193024..bd0adf39 100644 --- a/api-reference/v2/endpoint/sources-overview.mdx +++ b/api-reference/v2/endpoint/sources-overview.mdx @@ -7,11 +7,11 @@ description: "Quick reference for context management endpoints, their lifecycle, | Task | Endpoint | | :-- | :-- | -| Send text and conversations as context items | `POST /context/ingest` with `context[]` | +| Send text and conversations as context | `POST /context/ingest` with `context[]` | | Poll indexing progress | `GET /context/status` | -| Browse stored items | `POST /context/list` | +| Browse stored context | `POST /context/list` | | Read an item's stored content | `GET /context/inspect` | -| Delete items | `DELETE /context` | +| Delete context | `DELETE /context` | | Inspect graph relations | `GET /context/relations` | | Walk everything connected to one item | `GET /context/{id}/subgraph` | | Update an item's metadata without re-ingesting | `PATCH /context/{id}/metadata` | @@ -48,17 +48,17 @@ flowchart LR ## Core concepts -- **Items**: everything you ingest is a piece of context, a `text` or a `conversation`. One database holds all of them; collections partition them per user, team or project. See [Ingest context](/essentials/v2/ingest). -- **IDs**: each item has a `context_id`, yours or generated. The ingest response reports it as `results[].id`. Use it for polling status, inspecting content, deleting, and inspecting relations. -- **Attributes**: `attributes` are the declared, filterable fields from `database_metadata_schema`; `custom_attributes` are free-form and stored with the item. Filter queries with `attributes`. See [Attributes](/essentials/v2/attributes). -- **Enrichment**: on by default (`enrich: true`). HydraDB extracts entities, relations and preferences from each item into the [context graph](/essentials/v2/context-graphs); the extracted text comes back on query as `enrichment`, separate from the item's own `content`. -- **Declared relations**: any item can name the items it relates to with `forceful_relations`, so they surface together in `forceful_relations[]` on a `thinking` query. +- **Context**: everything you ingest is a piece of context, a `text` or a `conversation`. One database holds all of them; collections partition them per user, team or project. See [Ingest context](/essentials/v2/ingest). +- **IDs**: each context has a `context_id`, yours or generated. The ingest response reports it as `results[].id`. Use it for polling status, inspecting content, deleting, and inspecting relations. +- **Attributes**: `attributes` are the declared, filterable fields from `database_metadata_schema`; `custom_attributes` are free-form and stored with the context. Filter queries with `attributes`. See [Attributes](/essentials/v2/attributes). +- **Enrichment**: on by default (`enrich: true`). HydraDB extracts entities, relations and preferences from each context into the [context graph](/essentials/v2/context-graphs); the extracted text comes back on query as `enrichment`, separate from its own `content`. +- **Declared relations**: any context can name the contexts it relates to with `forceful_relations`, so they surface together in `forceful_relations[]` on a `thinking` query. ## Declared relations and attributes -Declared relations pre-wire item relationships at ingestion time so that related items surface together during retrieval, before the graph layer discovers connections on its own. Think of them as explicit "see also" links between your items. +Declared relations pre-wire relationships at ingestion time so that related contexts surface together during retrieval, before the graph layer discovers connections on its own. Think of them as explicit "see also" links between your contexts. -Declared attributes, sent on the same item, decide which items an `attributes` filter lets a query return. +Declared attributes, sent on the same context, decide which context an `attributes` filter lets a query return. ```json { @@ -80,7 +80,7 @@ Declared attributes, sent on the same item, decide which items an `attributes` f ## Related sections - [Ingest Context](/api-reference/v2/endpoint/ingest-context): the field reference -- [Usage: Ingest context](/essentials/v2/ingest): every item field, conversations, enrichment, declared relations +- [Usage: Ingest context](/essentials/v2/ingest): every context field, conversations, enrichment, declared relations - [Query](/api-reference/v2/endpoint/query-overview): retrieve ingested content diff --git a/api-reference/v2/endpoint/subgraph.mdx b/api-reference/v2/endpoint/subgraph.mdx index 03aa1189..3b8fe520 100644 --- a/api-reference/v2/endpoint/subgraph.mdx +++ b/api-reference/v2/endpoint/subgraph.mdx @@ -1,22 +1,20 @@ --- title: "Connected Subgraph" -api: "GET https://api.hydradb.com/context/subgraph" -playground: "none" -description: "Everything connected to one item: its thread, its replies, its parents and children, and the items it links to." +openapi: "api-reference/v2/openapi.json GET /context/subgraph" +description: "Everything connected to one context: its thread, its replies, its parents and children, and the context it links to." --- -import { Field } from "/snippets/field.jsx"; - This endpoint returns the **connected subgraph** of one ingested item: every item reachable from it through item-level relations, traversed breadth-first up to `depth` hops, together with the relations among those members and the structural graph around them (entities, comments, attachments, people). -It answers a different question from [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Relations are the entity-and-predicate triplets *extracted from text* (`PaymentsWorker → depends_on → OrdersDB`). The subgraph is about *items*: which Slack message replies to which, which page links to which, which ticket a comment belongs to. Use it after [Query](/api-reference/v2/endpoint/query) or [List Context](/api-reference/v2/endpoint/list-documents) when a single result is not enough and you need what surrounds it. +It answers a different question from [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Relations are the entity-and-predicate triplets *extracted from text* (`PaymentsWorker → depends_on → OrdersDB`). The subgraph is about *whole contexts*: which Slack message replies to which, which page links to which, which ticket a comment belongs to. Use it after [Query](/api-reference/v2/endpoint/query) or [List Context](/api-reference/v2/endpoint/list-documents) when a single result is not enough and you need what surrounds it. ```bash cURL -curl -G 'https://api.hydradb.com/context/slack_C0BE77_1788320073/subgraph' \ +curl -G 'https://api.hydradb.com/context/subgraph' \ -H "Authorization: Bearer " \ -H "API-Version: 2" \ + --data-urlencode "id=slack_C0BE77_1788320073" \ --data-urlencode "database=acme_corp" \ --data-urlencode "collection=eng_slack" \ --data-urlencode "depth=3" @@ -37,23 +35,12 @@ hydradb --output json subgraph slack_C0BE77_1788320073 | jq '.sources[].source_i The Python and TypeScript SDKs gain `context.subgraph()` with their next release, generated from this spec. Until then call the endpoint directly as above; the CLI and the MCP server already do. -## Path parameters - -| Name | Description | -| --- | --- | -| | The item to start from. Any `id` returned by Query, List Context or Ingest. URL-encode it if it contains reserved characters. An id containing a literal `/` cannot be written as one path segment; pass those as `GET /context/subgraph?id=...` instead. | +## Parameters -## Query parameters +- **`id`** is the item to start from: any `id` returned by Query, List Context or Ingest. This query-string form takes any id, including one that contains `/`. `GET /context/{id}/subgraph` is the same read with the id as a URL-encoded path segment; it cannot carry an id containing a literal `/`. +- **`depth`** ranges from `1` to `10` hops (default `5`). **`max_sources`** ranges from `1` to `1000` members (default `200`); when it clips the traversal, `is_truncated` is `true`. -| Name | Description | -| --- | --- | -| | Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | -| | Maximum traversal depth in hops. Range `1` to `10`. (default=`5`) | -| | Maximum number of members returned. Range `1` to `1000`. When this clips the traversal, `is_truncated` is `true`. (default=`200`) | -| | Principals to answer as (document ACLs). The subgraph then contains only items those principals may see, filtered at every hop. Repeated (`acl=a&acl=b`) or comma-separated. Omit for no ACL scoping. | - -## How items connect +## How contexts connect Every member except the start item records how the traversal found it: @@ -62,37 +49,6 @@ Every member except the start item records how the traversal found it: Traversal is breadth-first, so `depth` on each member is its distance from the start item. The start item itself is a member at depth `0`, with neither field set. -## Response - -| Name | Description | -| --- | --- | -| | The item the traversal started from (the `id` you passed). | -| | Every member of the subgraph, in the order it was reached, start item first (fields below). | -| | Item-level relations among the members, in the triplet shape of [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). | -| | The structural graph around the members: entities mentioned, comments, attachments, and who authored what. Same shape as `relations`. | -| | `true` when `auxiliary_relations` was cut off by a size limit. | -| | `true` when the traversal stopped before reaching every connected item: `max_sources` or a size limit was hit, or `depth` left items unexpanded. | -| | Deepest level at which a member was added. | -| | Human-readable result message. | -| | Whether the request succeeded. | - -Each member in `sources` carries: - -| Name | Description | -| --- | --- | -| | The member's item ID. | -| | Title of the item. | -| | Connector item category, for connector items. | -| | Connector the item came from (for example `slack`), for connector items. | -| | Provider-assigned identifier, for connector items. | -| | Thread the item belongs to, when it has one. | -| | Hops from the start item (`0` for the start item). | -| | `resolved` (ingested), `stub` (a relation points at it but it is not ingested yet) or `placeholder`. | -| | The `source_id` of the member this one was first reached from. Absent on the start item. | -| | How it was reached: a declared relation type, `same_thread`, `parent` or `child`. Absent on the start item. | - -Fields a member does not have are omitted. - ```json Success @@ -199,7 +155,7 @@ Fields a member does not have are omitted. ## Reading the response -- **`sources[]`** are the members, the start item included at `depth: 0`. Every `source_id` is an id you can pass to [Inspect Context](/api-reference/v2/endpoint/fetch-content) for the full content, or back to this endpoint to re-centre the subgraph on it. `discovered_via` on each member is another member's `source_id`, so the list is also a tree. +- **`sources[]`** are the members, the start item included at `depth: 0`. Every `source_id` is an id you can pass to [Inspect Context](/api-reference/v2/endpoint/fetch-content) for the full content, or back to this endpoint to re-centre the subgraph on it. `discovered_via` on each member is another member's `source_id`, so the list is also a tree. `hydration` is `resolved` (ingested), `stub` (a relation points at it but it is not ingested yet) or `placeholder`. Fields a member does not have are omitted. - **`relations[]`** are the item-level relations *among the members* (declared `relates_to` links, plus `same_thread` and `child_of`), in the same triplet shape as [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Their endpoints are `SOURCE` entities whose `entity_id` is the item's id. - **`auxiliary_relations[]`** is the structural graph around the members: which person sent a message, which entities are mentioned in it, which comments and attachments hang off it. These are recorded from the item itself, not extracted from text, so their `context` is empty. - **Not included:** the chunk-level entity relations that [Query](/api-reference/v2/endpoint/query) returns as graph paths in `graph[]`. Those are a different read. @@ -212,7 +168,7 @@ Fields a member does not have are omitted. - **An item nothing links to** comes back as a one-member subgraph: itself, at depth `0`, with `max_depth_reached: 0`. That is a real answer ("this stands alone"), distinct from an unknown id, which has no members. - **Bounding the traversal.** Threads and hierarchies can be large. `depth` bounds how far the walk goes; `max_sources` bounds how many members it returns. When `max_sources` clips it, `is_truncated` is `true` and the members you have are the ones closest to the start item. `auxiliary_truncated` reports the same for the structural graph. -- **Completeness.** An item's links populate once its `indexing_status` reaches `completed`. Items still in `graph_creation` may appear with fewer connections than they will have. +- **Completeness.** A context's links populate once its `indexing_status` reaches `completed`. Context still in `graph_creation` may appear with fewer connections than they will have. - **Cost.** One request fans out into a bounded series of graph reads, so it is rate-limited like a Query, not like a status poll.
diff --git a/api-reference/v2/endpoint/tenant-stats.mdx b/api-reference/v2/endpoint/tenant-stats.mdx index fcec1f3b..502c740a 100644 --- a/api-reference/v2/endpoint/tenant-stats.mdx +++ b/api-reference/v2/endpoint/tenant-stats.mdx @@ -1,13 +1,10 @@ --- title: "Database Stats" -api: "GET https://api.hydradb.com/databases/stats" -playground: "none" +openapi: "api-reference/v2/openapi.json GET /databases/stats" description: "Retrieve usage statistics for a database." --- -import { Field } from "/snippets/field.jsx"; - Get the indexed row count for a database. Counts aggregate across all collections in the database. The count is reported under two historical field names, `data.knowledge_collection` and `data.memory_collection`. The two `row_count` values are always equal; read either one. @@ -32,22 +29,6 @@ curl -X GET 'https://api.hydradb.com/databases/stats?database=my_first_database' -## Query parameters - -| Name | Description | -| --- | --- | -| | Database to report on. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | - -## Response - -| Name | Description | -| --- | --- | -| | The database the stats describe. | -| | Number of indexed chunks in the database. | -| | The same count as `knowledge_collection.row_count`, under a historical field name. | -| | Human-readable result message. | -| | Deprecated alias for `database`, carrying the same value. | - ```json Success @@ -108,7 +89,7 @@ curl -X GET 'https://api.hydradb.com/databases/stats?database=my_first_database' ## Behavior notes - **`row_count` is chunks, not context items.** One ingested item typically becomes several chunks (a long text item can produce 100\+ rows). To count distinct items, use [List Context](/api-reference/v2/endpoint/list-documents) with `page_size=1` and read `total`. + **`row_count` is chunks, not contexts.** One ingested context typically becomes several chunks (a long text can produce 100\+ rows). To count distinct contexts, use [List Context](/api-reference/v2/endpoint/list-documents) with `page_size=1` and read `total`. - **Empty databases report zero:** A database with nothing ingested yet reports `row_count: 0` under both field names. @@ -119,7 +100,7 @@ curl -X GET 'https://api.hydradb.com/databases/stats?database=my_first_database' **Related Resources** - - **List context items:** [List Context](/api-reference/v2/endpoint/list-documents) + - **List context:** [List Context](/api-reference/v2/endpoint/list-documents) - **List collections:** [List Collections](/api-reference/v2/endpoint/list-sub-tenants) - **Check provisioning:** [Database Status](/api-reference/v2/endpoint/tenant-status) - **Read more:** [Databases and collections](/essentials/v2/databases-and-collections) diff --git a/api-reference/v2/endpoint/tenant-status.mdx b/api-reference/v2/endpoint/tenant-status.mdx index 4d859537..8763ad34 100644 --- a/api-reference/v2/endpoint/tenant-status.mdx +++ b/api-reference/v2/endpoint/tenant-status.mdx @@ -1,14 +1,13 @@ --- title: "Database Status" -api: "GET https://api.hydradb.com/databases/status" -playground: "none" +openapi: "api-reference/v2/openapi.json GET /databases/status" description: "Check the readiness of a database's infrastructure." --- -import { Field } from "/snippets/field.jsx"; - Poll this endpoint until `data.infra.ready_for_ingestion` is `true`. That one flag is the readiness signal: the server derives it from the individual infrastructure flags below, so read it rather than combining them yourself. +`infra.vectorstore_status` reports vector store readiness under two historical field names, `knowledge` and `memories`. Both are `true` once the database is ready. + ```python Python SDK @@ -36,25 +35,6 @@ curl -X GET 'https://api.hydradb.com/databases/status?database=my_first_database -## Query parameters - -| Name | Description | -| --- | --- | -| | Database to check. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | - -## Response - -| Name | Description | -| --- | --- | -| | The database the status describes. | -| | Organization that owns the database. | -| | **The flag to poll.** `true` once the database is fully provisioned and ready to accept ingestion and serve queries. | -| | `true` once database setup has finished. Stays `false` while the database is still being created. | -| | `true` when the graph layer is healthy for this database. | -| | Vector store readiness, reported under two historical field names, `knowledge` and `memories`. Both are `true` once the database is ready. | -| | Human-readable result message. | -| | Deprecated alias for `database`, carrying the same value. | - ```json Success diff --git a/api-reference/v2/error-responses.mdx b/api-reference/v2/error-responses.mdx index f20a0270..c21605bf 100644 --- a/api-reference/v2/error-responses.mdx +++ b/api-reference/v2/error-responses.mdx @@ -261,14 +261,14 @@ Database creation is asynchronous. After `POST /databases`, poll [`GET /database - A `graph_payload` key matches no `context_id` in the same request. - An `acl` entry is not a valid principal. - The body is over 16 MiB. That is a `413` rather than a `400`, with the message `request body too large`. -- The request exceeds the limits: 100 items, 1 MiB of text per item, 8 MiB of text per request, 1,024 bytes per `title`, 4,000 characters of `instructions`, 16 KiB of `attributes` or 1 KiB of `custom_attributes` per item. +- The request exceeds the limits: 100 contexts in `context`, 1 MiB of text per context, 8 MiB of text per request, 1,024 bytes per `title`, 4,000 characters of `instructions`, 16 KiB of `attributes` or 1 KiB of `custom_attributes` per context. ### Empty query results Empty results are not always errors. Check these first: - Context status may still be `queued` or `processing`; poll [`GET /context/status`](/api-reference/v2/endpoint/source-status). -- An `attributes` filter may be too restrictive, or the items may not carry the attribute values you filter on. See [Attributes](/essentials/v2/attributes). +- An `attributes` filter may be too restrictive, or the context may not carry the attribute values you filter on. See [Attributes](/essentials/v2/attributes). - The query may be scoped to the wrong `database` or `collection` (formerly `tenant_id` / `sub_tenant_id`). To search several collections at once, send `collections`. ## Related sections diff --git a/api-reference/v2/index.mdx b/api-reference/v2/index.mdx index 241cea0d..ade94cf2 100644 --- a/api-reference/v2/index.mdx +++ b/api-reference/v2/index.mdx @@ -17,7 +17,7 @@ description: "Single reference to all HydraDB endpoints" | Group | Purpose | When to reach for it | |---|---|---| | [Databases](/api-reference/v2/endpoint/tenants-overview) | Create, monitor, and manage isolated workspaces | First step in any integration, and any time you need usage stats, provisioning status, or to tear down a workspace | -| [Context](/api-reference/v2/endpoint/sources-overview) | Ingest, list, fetch, delete, and inspect context items | Every time data flows into HydraDB: text, conversations, and lifecycle ops | +| [Context](/api-reference/v2/endpoint/sources-overview) | Ingest, list, fetch, delete, and inspect context | Every time data flows into HydraDB: text, conversations, and lifecycle ops | | [Query](/api-reference/v2/endpoint/query-overview) | Retrieve context with hybrid or text query | At query time: the only endpoint you call to feed an LLM | ## Core concepts @@ -26,10 +26,10 @@ description: "Single reference to all HydraDB endpoints" |---|---|---| | `database` | Your isolated workspace for data, metadata schema, and query. | Send it on every API call so HydraDB knows which workspace to read or write. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | | `collection` | Optional partition inside a database, often a user, team, account, or customer. | Use it when one database contains data for multiple users or customers. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). Read more about our [multi-tenant architecture](/essentials/v2/databases-and-collections) | -| [Context items](/essentials/v2/ingest) | A `text` or a `conversation`, sent in the `context` list of `POST /context/ingest`. | Everything you ingest. Shared context goes in a shared collection; a person's preferences go in their own. | +| [Context](/essentials/v2/ingest) | A `text` or a `conversation`, sent in the `context` list of `POST /context/ingest`. | Everything you ingest. Shared context goes in a shared collection; a person's preferences go in their own. | | `database_metadata_schema` | Database-level fields you define up front so metadata can be filtered or queried consistently. | Use it for stable fields like department, customer, region, plan, category, or compliance label. | | `attributes` | Declared, filterable fields on an item, matching `database_metadata_schema`; `custom_attributes` are free-form. | Send them at ingest; filter with `attributes` on `/query`. | -| `ids` | IDs returned by ingestion or visible from `/context/list`. | Use them when polling processing status, inspecting content, listing a specific subset, deleting items, or inspecting relations. | +| `ids` | IDs returned by ingestion or visible from `/context/list`. | Use them when polling processing status, inspecting content, listing a specific subset, deleting context, or inspecting relations. | ## End-to-end lifecycle @@ -39,7 +39,7 @@ flowchart LR subgraph Database Lifecycle [" "] direction LR A([Create Database])-->B([Wait for Provisioning]) - B-->C([Ingest Context Items]) + B-->C([Ingest Context]) C-->D([Verify Processing]) D-->E([Query Context]) E-->F([Pass to LLM]) @@ -100,14 +100,14 @@ SDK methods mirror the API: `client..()` maps to the correspondin | [`/databases/collections`](/api-reference/v2/endpoint/list-sub-tenants) | `GET` | `databases.collections` | List active collections | You partition data by user, team, customer, or account and need to inspect those partitions. | | [`/databases/collections`](/api-reference/v2/endpoint/delete-collection) | `DELETE` | `databases.delete_collection` | Delete a collection | You need to permanently remove one collection and its data. | | [`/databases/stats`](/api-reference/v2/endpoint/tenant-stats) | `GET` | `databases.stats` | Get usage statistics | You want to monitor object counts for a database. | -| [`/context/ingest`](/api-reference/v2/endpoint/ingest-context) | `POST` | `context.ingest` | Ingest context items | You are sending text or conversations. | +| [`/context/ingest`](/api-reference/v2/endpoint/ingest-context) | `POST` | `context.ingest` | Ingest context | You are sending text or conversations. | | [`/context/status`](/api-reference/v2/endpoint/source-status) | `GET` | `context.status` | Check processing status | You have IDs from ingestion and need to know when they are queryable. | | [`/context/inspect`](/api-reference/v2/endpoint/fetch-content) | `GET` | `context.inspect` | Read an item's stored content | You need the full stored content behind a `context_id`, such as the item a query chunk came from. For its title and attributes, use `POST /context/list` with `ids`. | -| [`/context/list`](/api-reference/v2/endpoint/list-documents) | `POST` | `context.list` | Browse items | You need pagination, filters, field projection, or a specific subset by `ids`. | +| [`/context/list`](/api-reference/v2/endpoint/list-documents) | `POST` | `context.list` | Browse context | You need pagination, filters, field projection, or a specific subset by `ids`. | | [`/context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) | `PATCH` | `context.update_source_metadata` | Update an item's metadata | You need to change one existing item's attributes (`database_metadata`) or custom attributes (`additional_metadata`) without re-ingesting. | -| [`/context`](/api-reference/v2/endpoint/delete-source) | `DELETE` | `context.delete` | Delete items | You need to remove one or more items by ID. | +| [`/context`](/api-reference/v2/endpoint/delete-source) | `DELETE` | `context.delete` | Delete context | You need to remove context by ID. | | [`/context/relations`](/api-reference/v2/endpoint/source-relations) | `GET` | `context.relations` | Inspect entity relationships | You need graph relations for an item or collection. | -| [`/context/{id}/subgraph`](/api-reference/v2/endpoint/subgraph) | `GET` | `context.subgraph` | Walk everything connected to one item | You need an item's thread, replies, parents, children and linked items. | +| [`/context/{id}/subgraph`](/api-reference/v2/endpoint/subgraph) | `GET` | `context.subgraph` | Walk everything connected to one context | You need a context's thread, replies, parents, children and linked context. | | [`/query`](/api-reference/v2/endpoint/query) | `POST` | `query` | Retrieve context | You need ranked chunks, graph paths, declared relations and a prompt-ready `llm_prompt`, with `hybrid` or `text` matching across one or more collections. | SDK method names are the Python names; TypeScript camelCases the multi-word ones (`update_metadata_schema` is `updateMetadataSchema`). Connector, webhook and feedback endpoints have their own pages: [Connectors](/api-reference/v2/endpoint/connectors-overview), [Register Webhook](/api-reference/v2/endpoint/register-webhook) and [Submit Feedback](/api-reference/v2/endpoint/submit-feedback). @@ -183,6 +183,6 @@ Rate limits apply per API key. For production deployments, build retry logic wit ## Next steps - **Build something:** [Quickstart](/get-started/v2/quickstart) walks through your first integration in five minutes -- **Understand the model:** [Core Concepts](/get-started/v2/core-concepts) explains databases, items, query and attributes +- **Understand the model:** [Core Concepts](/get-started/v2/core-concepts) explains databases, context, query and attributes - **Go deeper:** [Usage](/essentials/v2/query) covers each primitive in depth - **Install an SDK:** [Python](https://pypi.org/project/hydradb-sdk/) · [TypeScript](https://www.npmjs.com/package/@hydradb/sdk) diff --git a/api-reference/v2/sdks.mdx b/api-reference/v2/sdks.mdx index a39e816e..bce55616 100644 --- a/api-reference/v2/sdks.mdx +++ b/api-reference/v2/sdks.mdx @@ -59,7 +59,7 @@ The REST API uses **snake_case** for every request and response field. The Pytho | **TypeScript SDK** | camelCase | camelCase | `client.query({ maxResults: 8 })`, `result.data.llmPrompt` | -**Item keys stay snake_case in every language.** `client.context.ingest` takes the item list as a JSON string in the `context` field, so the keys inside each item (`context_id`, `happened_at`, `custom_attributes`) are the wire names in TypeScript too. +**Context keys stay snake_case in every language.** `client.context.ingest` takes the list as a JSON string in the `context` field, so the keys inside each context (`context_id`, `happened_at`, `custom_attributes`) are the wire names in TypeScript too. `database` and `collection` are the current field names (formerly `tenant_id` and `sub_tenant_id`). The old names remain accepted as deprecated aliases. @@ -83,7 +83,7 @@ SDK methods are grouped by the endpoint they call: ### Context -`client.context.*` covers the lifecycle of context items: ingesting, polling, inspecting, listing, deleting and exploring the graph. +`client.context.*` covers the lifecycle of context: ingesting, polling, inspecting, listing, deleting and exploring the graph. | Method | Endpoint | |---|---| @@ -236,7 +236,7 @@ console.log(result.data?.results?.map((r) => r.id)); ``` -The response is `202 Accepted` with one result per item; `results[].id` is the item's `context_id`. Up to 100 items fit in one call. Every item field, default and limit is on [Ingest Context](/api-reference/v2/endpoint/ingest-context). +The response is `202 Accepted` with one result per context; `results[].id` is its `context_id`. Up to 100 contexts fit in one call. Every context field, default and limit is on [Ingest Context](/api-reference/v2/endpoint/ingest-context). ### Verify processing @@ -393,7 +393,7 @@ Chunks carry no title or URL; `llm_prompt` prints them for the model. To show an ```python Python SDK -# List the items in a collection, 50 per page. +# List the context in a collection, 50 per page. listing = client.context.list( database="my_first_database", collection="support", @@ -422,7 +422,7 @@ relations = client.context.relations( id="refund-policy", ) -# Delete items by context_id. +# Delete context by context_id. deleted = client.context.delete( database="my_first_database", collection="support", @@ -430,7 +430,7 @@ deleted = client.context.delete( ) ``` ```typescript TypeScript SDK -// List the items in a collection, 50 per page. +// List the context in a collection, 50 per page. const listing = await client.context.list({ database: "my_first_database", collection: "support", @@ -459,7 +459,7 @@ const relations = await client.context.relations({ id: "refund-policy", }); -// Delete items by context_id. +// Delete context by context_id. const deleted = await client.context.delete({ database: "my_first_database", collection: "support", @@ -581,5 +581,5 @@ Whether you're using TypeScript, Python, VS Code, PyCharm, or any modern IDE, th - [API Reference](/api-reference/v2): complete endpoint documentation - [Error Responses](/api-reference/v2/error-responses): HTTP codes, error codes, retry patterns - [Quickstart](/get-started/v2/quickstart): build your first integration in five minutes -- [Ingest context](/essentials/v2/ingest): every item field, conversations and enrichment +- [Ingest context](/essentials/v2/ingest): every context field, conversations and enrichment - [Query](/essentials/v2/query): every field of `POST /query` and its response diff --git a/essentials/v2/access-control.mdx b/essentials/v2/access-control.mdx index a98e8fd0..a3e66a12 100644 --- a/essentials/v2/access-control.mdx +++ b/essentials/v2/access-control.mdx @@ -50,7 +50,7 @@ Limits: 1000 principals per document, 256 characters per principal. Past that, u ### At ingest, on any item -Each item in `context` accepts an `acl` list, whether it is a `text` or a `conversation`: +Each entry in `context` accepts an `acl` list, whether it is a `text` or a `conversation`: ```json { @@ -230,7 +230,7 @@ Check the principal forms on both sides. `group:slack:C0123` on the document onl ## Related - [Connectors](/essentials/v2/connectors): syncing app data, and per-resource ACL rules -- [Ingest context](/essentials/v2/ingest#9-restricting-an-item): the `acl` item field +- [Ingest context](/essentials/v2/ingest#9-restricting-a-context): the `acl` field on each context - [Query](/essentials/v2/query): the `acl` field alongside every other retrieval parameter - [Attributes](/essentials/v2/attributes): filtering by attributes, a different question from permission - [Databases and collections](/essentials/v2/databases-and-collections): the isolation boundary ACLs work inside diff --git a/essentials/v2/api-results.mdx b/essentials/v2/api-results.mdx index be7f8531..debc6c9f 100644 --- a/essentials/v2/api-results.mdx +++ b/essentials/v2/api-results.mdx @@ -145,7 +145,7 @@ FAQ: refunds to a card take 5 to 7 business days to appear. | --- | --- | --- | | `# Query results` | The query; an `**Interpreted:**` line when an alias or a resolved reference widened it; a `**Found:**` line counting what follows; a `**Note:**` line when a temporal, source or profile lookup degraded or was truncated; and, when there is a result, the instruction to cite it by its number | None | | `## Results` | `chunks[]`, in ranked order: a `### 1. title` heading, a line with relevance (`score`), collection, type and category (`enrichment_kind`), a line with the id (`context_id`) and last-updated date, the `content`, then `**Enrichment:**` (`enrichment`). Results are separated by `---`. | `[1]`, `[2]`, ... | -| `## Forceful relations` | `forceful_relations[]`, the items the hits declared with `forceful_relations` at ingest: a guide line, then `### R1. title` blocks laid out like results, with `**Linked from:**` (`via.from`) in place of relevance | `[R1]`, `[R2]`, ... | +| `## Forceful relations` | `forceful_relations[]`, the context the hits declared with `forceful_relations` at ingest: a guide line, then `### R1. title` blocks laid out like results, with `**Linked from:**` (`via.from`) in place of relevance | `[R1]`, `[R2]`, ... | | `## Related facts` | `graph[]`, one line per path: its chain of hops (`**A** -pred→ **B**`), its relevance after reranking in parentheses when it has one (`(relevance 0.81)`; a path with no reranked score has no parenthetical), and the results its hops were extracted from, with the `path_summary` indented under it unless it only narrates the chain | `[P1]`, `[P2]`, ... in `graph[]` order; each line also cites its results | | `## Temporal facts` | For a "how long between" question, a `**Duration:**` line first (the computed days, whether approximate, and the two dated facts). Then the dated facts behind `chunks[].temporal`, with window, fact type, precision and status, then the evidence phrase after a `;` | None; each fact cites its result, or names its source id when that chunk is not a result | | `## Source facts` | App-native facts about the sources behind the results: who acted and in what role, where, which thread and connector, when synced. Prompt only: no JSON key carries them | None; each fact cites its result | @@ -166,7 +166,7 @@ Render a UI, rerank, or apply your own rules from the three structured keys. The | `chunks[].content` | The matched text, verbatim. | | `chunks[].enrichment` | What enrichment extracted from that chunk (a preference, a fact), as a string. | | `chunks[].score` | Relevance, for your own thresholds. | -| `chunks[].received_at` | When HydraDB received the item, as an RFC 3339 timestamp; omitted when none is recorded. Not the item's `happened_at`. | +| `chunks[].received_at` | When HydraDB received the context, as an RFC 3339 timestamp; omitted when none is recorded. Not its `happened_at`. | | `graph[].path_summary` | One sentence per graph path; `graph[].triplets` for the steps and `graph[].origin` for how it was found. To show a path under its chunk, see [Attaching graph paths to chunks](/essentials/v2/query#attaching-graph-paths-to-chunks). | | `forceful_relations[]` | Chunks linked at ingest with `forceful_relations`, each with the `via` link that brought it in. | @@ -201,7 +201,7 @@ for (const rel of result.data.forcefulRelations) { ## 4. Showing source details -A chunk carries only `chunk_id`, `context_id`, `score`, `content`, `enrichment`, `enrichment_kind`, `received_at` (when HydraDB received the item) and `temporal`. It has no title, url, collection or attributes. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show an item's title, timestamp or attributes in your own UI, list it by its `context_id` with [`POST /context/list`](/api-reference/v2/endpoint/list-documents): +A chunk carries only `chunk_id`, `context_id`, `score`, `content`, `enrichment`, `enrichment_kind`, `received_at` (when HydraDB received the context) and `temporal`. It has no title, url, collection or attributes. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show an item's title, timestamp or attributes in your own UI, list it by its `context_id` with [`POST /context/list`](/api-reference/v2/endpoint/list-documents): ```bash curl -X POST 'https://api.hydradb.com/context/list' \ @@ -211,7 +211,7 @@ curl -X POST 'https://api.hydradb.com/context/list' \ -d '{ "database": "acme", "collection": "company", "ids": ["refund-policy"] }' ``` -The row carries `title`, `timestamp` and the item's attributes (under `metadata` and `additional_metadata`, the list response's names for `attributes` and `custom_attributes`). [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) with the same `context_id` returns the item's stored content. +The row carries `title`, `timestamp` and the context's attributes (under `metadata` and `additional_metadata`, the list response's names for `attributes` and `custom_attributes`). [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) with the same `context_id` returns the item's stored content. Fetch it lazily, when a citation is opened, rather than for every chunk on every query. diff --git a/essentials/v2/architecture.mdx b/essentials/v2/architecture.mdx index feb8f47f..4dd42341 100644 --- a/essentials/v2/architecture.mdx +++ b/essentials/v2/architecture.mdx @@ -14,7 +14,7 @@ HydraDB organizes its work into three logical planes. You interact only with the | Plane | What it handles | Endpoints | |---|---|---| | **Control** | API authentication, database lifecycle, provisioning, and status | [`/databases`](/api-reference/v2/endpoint/tenants-overview) family | -| **Ingestion** | Context item writes, connector syncs, parsing, chunking, embedding, and graph construction | [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context), [`GET /context/status`](/api-reference/v2/endpoint/source-status) | +| **Ingestion** | Context writes, connector syncs, parsing, chunking, embedding, and graph construction | [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context), [`GET /context/status`](/api-reference/v2/endpoint/source-status) | | **Retrieval** | Hybrid query, attribute filtering, graph context, keyword (BM25) search, and response shaping | [`POST /query`](/api-reference/v2/endpoint/query), [`GET /context/relations`](/api-reference/v2/endpoint/source-relations) | ```mermaid @@ -79,7 +79,7 @@ flowchart LR Two details to notice in the diagram: -- **One ingest endpoint, one database of context items.** [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context) takes `context` items, text or conversations, and writes them to the database. [Connectors](/essentials/v2/connectors) sync provider content into the same database. Collections partition it per user, team or project. +- **One ingest endpoint, one database of context.** [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context) takes a `context` list of text or conversations and writes them to the database. [Connectors](/essentials/v2/connectors) sync provider content into the same database. Collections partition it per user, team or project. - **One query endpoint, every retrieval method.** [`POST /query`](/api-reference/v2/endpoint/query) is the only retrieval entry point. `collections` decides where to look and `query_by` how to match; see [Query](/essentials/v2/query) for the full picture. --- @@ -90,7 +90,7 @@ Ingestion is asynchronous. A successful upload means HydraDB accepted the work a ```mermaid flowchart LR - Upload([Ingest context items]) + Upload([Ingest context]) Queued([Queued]) Processing([Processing]) Graph([Graph Creation]) @@ -127,7 +127,7 @@ Here's the canonical end-to-end flow. Each step links to the endpoint that owns 1. **Create a database** with [`POST /databases`](/api-reference/v2/endpoint/create-tenant): your isolated workspace, optionally with a [metadata schema](/essentials/v2/attributes) declared up front. 2. **Wait for provisioning** by polling [`GET /databases/status`](/api-reference/v2/endpoint/tenant-status) until `infra.ready_for_ingestion` is `true`. -3. **Ingest content** with [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context): `context` items, text or conversations, into a shared collection or a person's own. See [Ingest context](/essentials/v2/ingest) for every item field. +3. **Ingest content** with [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context): a `context` list of text or conversations, into a shared collection or a person's own. See [Ingest context](/essentials/v2/ingest) for every field. 4. **Watch indexing finish** by polling [`GET /context/status`](/api-reference/v2/endpoint/source-status) until each `id` reaches `completed` (or `graph_creation` if you don't need graph traversal). 5. **Query** with [`POST /query`](/api-reference/v2/endpoint/query). Name the collections to search, weighted if you like, and pair with `query_by: "hybrid"` (default) or `"text"` (BM25, with `operator`). Inject the returned `llm_prompt` into your model call. The mechanics live in [Query](/essentials/v2/query). @@ -159,7 +159,7 @@ The deeper trade-offs (when to spin up a new database vs. a new collection, how 2. **Filter before ranking.** Apply `attributes` to narrow the candidate set (see [Attributes](/essentials/v2/attributes)). 3. **Retrieve.** Run hybrid retrieval over the semantic vector store and the keyword (BM25) index, or BM25-only retrieval when `query_by: "text"`. 4. **Blend.** Use `alpha` to weight semantic vs. keyword (BM25) contributions (`1.0` = pure semantic, `0.0` = pure BM25). -5. **Enrich.** With `graph_context` on (the default), traverse the [context graph](/essentials/v2/context-graphs) and attach related paths. When `mode: "thinking"`, expand the query, rerank, and pull in the relations items declared at ingest. +5. **Enrich.** With `graph_context` on (the default), traverse the [context graph](/essentials/v2/context-graphs) and attach related paths. When `mode: "thinking"`, expand the query, rerank, and pull in the relations declared at ingest. 6. **Shape the response.** Return ranked `chunks`, graph paths in `graph`, declared links in `forceful_relations`, and `llm_prompt`, the same context as one prompt-ready string with citation labels. The response is *retrieved context*, not a final LLM answer. You inject `llm_prompt` into your own agent or model prompt; see [How to Use API Results](/essentials/v2/api-results). @@ -174,9 +174,9 @@ A short cheat sheet for the parameters you'll touch most often, and where each o |---|---|---| | `database` | Every call | Selects the isolated workspace. Required on every request. | | `collection` | Ingest + query | Narrows data to a user, team, or workspace inside the database. Use a single collection for writes and single-scope queries. | -| `collections` | Query | Preferred query-time collection selector. Use a single-item list, a multi-scope list with equal weights, or a weighted object for fanout ranking. | +| `collections` | Query | Preferred query-time collection selector. Use a one-element list, a multi-scope list with equal weights, or a weighted object for fanout ranking. | | `attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Declared, filterable fields on an item. Must match the [database metadata schema](/essentials/v2/attributes) declared at database creation. | -| `custom_attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Free-form per-item fields. Stored with the item; not filterable with `attributes`. | +| `custom_attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Free-form fields per context. Stored with the context; not filterable with `attributes`. | | `attributes` | [Query](/api-reference/v2/endpoint/query) | Deterministic narrowing with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on declared attributes. | | `alpha` | [Query](/api-reference/v2/endpoint/query) | Blends semantic vs. keyword (BM25) scores in `query_by: "hybrid"`. | | `graph_context` | [Query](/api-reference/v2/endpoint/query) | On by default: returns relation paths from the [context graph](/essentials/v2/context-graphs) in `graph[]`. | @@ -198,7 +198,7 @@ HydraDB separates **write-time** work from **query-time** work. Uploads return q - [Core Concepts](/get-started/v2/core-concepts): the primitives, from databases and collections to the context graph - [Quickstart](/get-started/v2/quickstart): build your first integration in five minutes -- [Ingest context](/essentials/v2/ingest): one database of context items, text or conversations +- [Ingest context](/essentials/v2/ingest): one database of context, text or conversations - [Query](/essentials/v2/query): deep dive on `POST /query` - [Databases and collections](/essentials/v2/databases-and-collections): scoping patterns and pitfalls - [Context Graphs](/essentials/v2/context-graphs): how the graph layer enriches retrieval diff --git a/essentials/v2/attributes.mdx b/essentials/v2/attributes.mdx index 3a6b4590..95150829 100644 --- a/essentials/v2/attributes.mdx +++ b/essentials/v2/attributes.mdx @@ -1,6 +1,6 @@ --- title: "Attributes" -description: "Declare filterable attributes in the database schema, attach attributes and custom attributes to items at ingest, and filter queries with the attributes operator language." +description: "Declare filterable attributes in the database schema, attach attributes and custom attributes to context at ingest, and filter queries with the attributes operator language." --- Attributes are structured values you attach to each item you ingest. Use them when you already know a hard constraint before retrieval runs, such as `department=legal`, `region=us`, `status=published` or `priority >= 5`. @@ -133,7 +133,7 @@ await client.databases.create({ | --- | --- | --- | | `name` | string | The attribute key. Must start with a letter and contain only letters, numbers and underscores, at most 255 characters. Reserved system names such as `chunk_id`, `source_id`, `source_title` and `description` are rejected; the error lists every reserved name. | | `data_type` | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the friendly aliases `string`, `boolean`, `integer`, `float`, `object` | Defaults to `VARCHAR`. `array` is **not** supported and is rejected with `400`; see [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | -| `max_length` | integer | Maximum length of a `VARCHAR` value. Default `1024`, maximum `65535`. It sizes one field and cannot be raised later, so declare it large enough up front. It is **not** the per-item budget for all attributes; for that see [Size limits](#size-limits). | +| `max_length` | integer | Maximum length of a `VARCHAR` value. Default `1024`, maximum `65535`. It sizes one field and cannot be raised later, so declare it large enough up front. It is **not** the budget for all attributes on one context; for that see [Size limits](#size-limits). | | `enable_match` | boolean | Turns on keyword matching (a text analyzer) for the field. An `attributes` filter works on every declared field whether or not this is set. | | `enable_dense_embedding` | boolean | Adds dense semantic search over a `VARCHAR` field. | | `enable_sparse_embedding` | boolean | Adds sparse (BM25) keyword search over a `VARCHAR` field. | @@ -171,14 +171,14 @@ The update is additive only: `GET /databases/{database}/metadata-schema` returns the current `fields` in the same shape `add_fields` accepts. - Items ingested before a field existed have no value for it, so a comparison on the new field does not match them. Re-ingest those items with the value to include them. + Context ingested before a field existed has no value for it, so a comparison on the new field does not match it. Re-ingest that context with the value to include it. --- ## 3. Attach attributes at ingest -Send `attributes` and `custom_attributes` on each item in `context` on [`POST /context/ingest`](/essentials/v2/ingest). The SDKs send the same item array, as a JSON string, in the `context` form field; keys inside each item stay snake_case in every language. +Send `attributes` and `custom_attributes` on each entry in `context` on [`POST /context/ingest`](/essentials/v2/ingest). The SDKs send the same array, as a JSON string, in the `context` form field; keys inside each item stay snake_case in every language. ```bash cURL @@ -344,11 +344,11 @@ const result = await client.query({ ``` -Query chunks do not carry attributes. The filter decides which items can appear; see [Query](/essentials/v2/query) for what the response contains. +Query chunks do not carry attributes. The filter decides which context can appear; see [Query](/essentials/v2/query) for what the response contains. ### Operators -| Operator | Operand | Matches items whose value... | +| Operator | Operand | Matches context whose value... | | --- | --- | --- | | a bare value | a value of the field's type | equals it. `{"department": "legal"}` is the same as `{"department": {"$eq": "legal"}}`. | | `$eq` | a value of the field's type | equals it. | @@ -390,7 +390,7 @@ Operators combine and nest: | Equality | Exact, against the **whole** stored value. Strings are case-sensitive. | | Value types | Every operand must match the field's declared type: a string for `VARCHAR`, `true` or `false` for `BOOL`, a whole number for the integer types, a number for `FLOAT` and `DOUBLE`. `{"priority": "7"}` on an `INT64` field is a `400`, not an empty result. | | `JSON` fields | Only `$exists` applies. Any other operator on a `JSON` field is a `400`. | -| Missing values | An item with no value for a field never matches a comparison on that field. `$ne`, `$nin` and `$not` exclude it too. To keep such items, say so: `{"$or": [{"region": {"$ne": "eu"}}, {"region": {"$exists": false}}]}`. | +| Missing values | A context with no value for a field never matches a comparison on that field. `$ne`, `$nin` and `$not` exclude it too. To keep such context, say so: `{"$or": [{"region": {"$ne": "eu"}}, {"region": {"$exists": false}}]}`. | | Field names | Must be declared in `database_metadata_schema`. An undeclared field is a `400` (`unknown attribute`), never silently ignored. A reserved system column is a `400`. On a database created without any schema, every field is compared as a string. | | Custom attributes | Cannot be filtered with `attributes`. Naming the custom attributes namespace inside `attributes` is a `400`. | | Empty pieces | An empty object, an empty operator object, or an empty `$and`, `$or`, `$in` or `$nin` array is a `400`, not a filter that matches everything. | @@ -415,7 +415,7 @@ Operators combine and nest: `$in` runs the other way round: it asks whether the item's one value is among the values you list. -If you need to select items by one member of a set: +If you need to select context by one member of a set: - Give each member you filter on its own `BOOL` attribute, such as `"tag_billing": true`, and filter with `{"tag_billing": true}`. This counts against the 32-field limit, so it suits a small, known set. - If the set is really "who may see this item", use `acl` instead. See [Access control](/essentials/v2/access-control). @@ -490,9 +490,9 @@ Behavior: --- -## 6. Browse items by attribute +## 6. Browse context by attribute -To page through items rather than run retrieval, use [`POST /context/list`](/api-reference/v2/endpoint/list-documents). Its `filters` object matches stored values exactly; the endpoint page documents its keys, `include_fields` and paging. +To page through context rather than run retrieval, use [`POST /context/list`](/api-reference/v2/endpoint/list-documents). Its `filters` object matches stored values exactly; the endpoint page documents its keys, `include_fields` and paging. --- @@ -500,13 +500,13 @@ To page through items rather than run retrieval, use [`POST /context/list`](/api | Symptom | Cause | Fix | | --- | --- | --- | -| Query returns `400 unknown attribute` | The field is not declared in `database_metadata_schema` | Declare it with `PATCH /databases/{database}/metadata-schema`, then re-ingest the items that should carry it. | +| Query returns `400 unknown attribute` | The field is not declared in `database_metadata_schema` | Declare it with `PATCH /databases/{database}/metadata-schema`, then re-ingest the context that should carry it. | | Ingest returns `400` naming an undeclared field | An `attributes` key is not in the schema | Declare the field, or move it to `custom_attributes` if you never filter on it. | | A filter on a custom attribute is rejected | `attributes` cannot filter on `custom_attributes` | Declare the field, send it in `attributes`, and re-ingest. | | `400 value for "priority" does not match its type` | The operand's JSON type differs from the declared type, such as `"7"` for an `INT64` field | Send the declared type: `{"priority": 7}`. | | `$in` does not find an item whose field holds several values | There is no containment | See [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | -| `$ne` or `$not` drops items that have no value for the field | Missing values never match a comparison | Add `{"field": {"$exists": false}}` under `$or`. | -| Query returns 0 results after adding a filter | Over-scoping: the combined constraints exclude everything, or the items predate the field | Start with one constraint, add the others one at a time, and check with `$exists`. | +| `$ne` or `$not` drops context that has no value for the field | Missing values never match a comparison | Add `{"field": {"$exists": false}}` under `$or`. | +| Query returns 0 results after adding a filter | Over-scoping: the combined constraints exclude everything, or the context predates the field | Start with one constraint, add the others one at a time, and check with `$exists`. | | A value edited with `PATCH /context/{id}/metadata` still filters as the old value | The filter runs against the values indexed at ingest | Re-ingest the item with `upsert: true` and the same `context_id`. | | A schema field cannot be changed | Declared fields are immutable | Add a new field, or create a new database with the corrected schema and re-ingest. | | Adding a field with `enable_dense_embedding` or `enable_sparse_embedding` returns `400` | Embedding flags can only be set at database creation | Create a new database with the final schema and re-ingest. | @@ -532,13 +532,13 @@ To page through items rather than run retrieval, use [`POST /context/list`](/api ## Related -- [Ingest context](/essentials/v2/ingest): every item field, including `attributes` and `custom_attributes` +- [Ingest context](/essentials/v2/ingest): every context field, including `attributes` and `custom_attributes` - [Query](/essentials/v2/query): how `attributes` sits alongside ranking, graph and forceful relations - [Databases and collections](/essentials/v2/databases-and-collections): partitioning versus filtering - [Access control](/essentials/v2/access-control): restricting who may retrieve an item, which is not an attribute filter - [Create Database API reference](/api-reference/v2/endpoint/create-tenant): the full `database_metadata_schema` reference - [Ingest API reference](/api-reference/v2/endpoint/ingest-context): the full item reference - [Query API reference](/api-reference/v2/endpoint/query): the full `attributes` request reference -- [List Context](/api-reference/v2/endpoint/list-documents): browsing items with exact-match filters +- [List Context](/api-reference/v2/endpoint/list-documents): browsing context with exact-match filters - [Update Source Metadata](/api-reference/v2/endpoint/update-source-metadata): in-place value edits - [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema): additive schema changes diff --git a/essentials/v2/bring-your-own-graph.mdx b/essentials/v2/bring-your-own-graph.mdx index dfbc854d..1ceffb85 100644 --- a/essentials/v2/bring-your-own-graph.mdx +++ b/essentials/v2/bring-your-own-graph.mdx @@ -24,15 +24,15 @@ Pick the right tool: | You want... | Use | | --- | --- | | HydraDB to discover relationships for you | [Context graphs](/essentials/v2/context-graphs) (auto-extraction, the default) | -| To declare links **between whole items** | `forceful_relations` on an item. See [Declared relations](/essentials/v2/ingest#10-declared-relations). | +| To declare links **between whole contexts** | `forceful_relations` on a context. See [Declared relations](/essentials/v2/ingest#10-declared-relations). | | To supply the **full entity and relation graph for one item** | **Bring Your Own Graph** (this page) | -| A standalone property graph you write and read with **Cypher**, separate from context items | [Cypher Graph Collections](/essentials/v2/graph-collections-byog) | +| A standalone property graph you write and read with **Cypher**, separate from ingested context | [Cypher Graph Collections](/essentials/v2/graph-collections-byog) | --- ## 3. The `graph_payload` shape -`graph_payload` sits at the top level of the ingest request, next to `context`. It is a **map keyed by `context_id`**, where each value is that item's graph: an `entities` map and a `relations` list. Attach graphs to several items in one request by adding more keys. +`graph_payload` sits at the top level of the ingest request, next to `context`. It is a **map keyed by `context_id`**, where each value is that context's graph: an `entities` map and a `relations` list. Attach graphs to several contexts in one request by adding more keys. ```json { @@ -67,7 +67,7 @@ Pick the right tool: - **No `chunk_id`:** you never supply chunk ids. HydraDB links your relations to the item's chunks server-side. - Entity names are **normalized (lowercased)** so they match at query time, just like extracted entities. Entities that no relation references are dropped. -In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, where `graph_payload` is a JSON string next to the `context` field; see the [examples](#6-example-several-items-in-one-request). +In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, where `graph_payload` is a JSON string next to the `context` field; see the [examples](#6-example-several-contexts-in-one-request). --- @@ -92,13 +92,13 @@ In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, wh | Relation `context` length | ≤ 2,000 bytes (UTF-8) | | Entity key, `name`, `type`, `namespace`, `identifier`, `predicate` and `temporal_details` length | ≤ 256 bytes (UTF-8) each | -The request itself keeps the normal ingest limits: at most 100 items, 1 MiB of text per item and 8 MiB of text per request. A JSON body, `graph_payload` included, is capped at 16 MiB (`413` beyond it), so split very large graphs across requests. See [Ingest context](/essentials/v2/ingest#limits-and-unrecognised-fields). +The request itself keeps the normal ingest limits: at most 100 contexts, 1 MiB of text per context and 8 MiB of text per request. A JSON body, `graph_payload` included, is capped at 16 MiB (`413` beyond it), so split very large graphs across requests. See [Ingest context](/essentials/v2/ingest#limits-and-unrecognised-fields). --- -## 6. Example: several items in one request +## 6. Example: several contexts in one request -`graph_payload` is a map, so one request can carry graphs for several items at once. Here three items, each keyed by its own `context_id`. Then query, and each item's triplets surface. +`graph_payload` is a map, so one request can carry graphs for several contexts at once. Here three contexts, each keyed by its own `context_id`. Then query, and each context's triplets surface. @@ -152,7 +152,7 @@ curl -X POST 'https://api.hydradb.com/context/ingest' \ ```python Python SDK import json -items = [ +contexts = [ {"context_id": "billing-policy", "title": "Billing policy", "text": "Alice Carter owns the billing policy. Invoices are issued on the first business day of each month."}, {"context_id": "deploy-runbook", "title": "Deploy runbook", @@ -196,13 +196,13 @@ graphs = { client.context.ingest( database="acme_corp", - context=json.dumps(items), + context=json.dumps(contexts), graph_payload=json.dumps(graphs), ) ``` ```typescript TypeScript SDK -const items = [ +const contexts = [ { context_id: "billing-policy", title: "Billing policy", text: "Alice Carter owns the billing policy. Invoices are issued on the first business day of each month." }, { context_id: "deploy-runbook", title: "Deploy runbook", @@ -246,7 +246,7 @@ const graphs = { await client.context.ingest({ database: "acme_corp", - context: JSON.stringify(items), + context: JSON.stringify(contexts), graphPayload: JSON.stringify(graphs), }); ``` @@ -310,6 +310,6 @@ The same facts also appear under `## Related facts` in `llm_prompt`. See [Query] ## Related - [Context graphs](/essentials/v2/context-graphs): the auto-extracted graph BYOG replaces. HydraDB builds it for you; BYOG lets you supply it. -- [Ingest context](/essentials/v2/ingest): every item field, including `forceful_relations` for links between items +- [Ingest context](/essentials/v2/ingest): every context field, including `forceful_relations` for links between contexts - [Ingest context API reference](/api-reference/v2/endpoint/ingest-context): the `graph_payload` field reference - [Query](/essentials/v2/query): how chunks and `graph[]` are retrieved together diff --git a/essentials/v2/connectors.mdx b/essentials/v2/connectors.mdx index 4d183ed8..dd569854 100644 --- a/essentials/v2/connectors.mdx +++ b/essentials/v2/connectors.mdx @@ -3,7 +3,7 @@ title: "Connectors" description: "How HydraDB connectors continuously sync external app data into your database as searchable context." --- -Connectors bring external app data into HydraDB automatically. Instead of ingesting it yourself, you authenticate once, pick which resources to sync, and HydraDB continuously syncs provider content into your database as searchable context, queried alongside the items you [ingest](/essentials/v2/ingest). +Connectors bring external app data into HydraDB automatically. Instead of ingesting it yourself, you authenticate once, pick which resources to sync, and HydraDB continuously syncs provider content into your database as searchable context, queried alongside the context you [ingest](/essentials/v2/ingest). --- diff --git a/essentials/v2/context-graphs.mdx b/essentials/v2/context-graphs.mdx index 9c886536..613ee3c9 100644 --- a/essentials/v2/context-graphs.mdx +++ b/essentials/v2/context-graphs.mdx @@ -17,7 +17,7 @@ Context graphs augment retrieval. They do not replace it. With `graph_context: true` on a query (the default), HydraDB returns `graph[]` alongside the retrieved chunks: the paths through the graph that connect the query to the results and the results to each other. Set `graph_context: false` when you only need ranked chunks; `graph` is then `[]`. -This helps your LLM reason about questions that require connecting information across several items. Similarity retrieval returns relevant content; the context graph surfaces how that content fits together. +This helps your LLM reason about questions that require connecting information across several sources. Similarity retrieval returns relevant content; the context graph surfaces how that content fits together. --- @@ -37,7 +37,7 @@ Skip them for direct factual lookups. Graph traversal adds response size and can Context graphs are hybrid: relationships are extracted at ingestion time and traversed at query time. -**At ingestion**, with `enrich: true` (the default), HydraDB extracts entities and relations from each item and stores them in the graph. An item can also declare its own links to other items with [`forceful_relations`](/essentials/v2/ingest#10-declared-relations), or skip extraction and supply its entities and relations with [`graph_payload`](/essentials/v2/ingest#11-bring-your-own-graph). +**At ingestion**, with `enrich: true` (the default), HydraDB extracts entities and relations from each context and stores them in the graph. A context can also declare its own links to other contexts with [`forceful_relations`](/essentials/v2/ingest#10-declared-relations), or skip extraction and supply its entities and relations with [`graph_payload`](/essentials/v2/ingest#11-bring-your-own-graph). **At query**, with `graph_context: true`: @@ -59,9 +59,9 @@ Example: `Alex`, `prefers`, `short answers`, from chunk `ck_9f2`. **Evidence.** `relation.chunk_id` names the chunk every step of a path was extracted from. Group hops by it against `chunks[].chunk_id` to show a chunk's relations under that chunk: a `chunk_relation` path is only returned when one of its hops came from a returned chunk (or a `forceful_relations` chunk), and hangs under that chunk; a `query_path` hop may also sit under the chunk it came from. See [Attaching graph paths to chunks](/essentials/v2/query#attaching-graph-paths-to-chunks). -**Forceful relations.** Links between items rather than between entities, which you declare at ingest with `forceful_relations`. A `thinking` query follows them, and they come back in `forceful_relations[]`, not in `graph[]`. +**Forceful relations.** Links between whole contexts rather than between entities, which you declare at ingest with `forceful_relations`. A `thinking` query follows them, and they come back in `forceful_relations[]`, not in `graph[]`. -**Connected subgraph.** The graph also holds relations between items themselves: a Slack reply and the message it answers, a page and the pages it links to, a comment and its ticket. Given one item's id, [Connected Subgraph](/api-reference/v2/endpoint/subgraph) walks those links breadth-first and returns everything reachable with the relations among them. Reach for it when one query result is not enough and you need what surrounds it; it is also what the dashboard's **Subgraph** button opens. +**Connected subgraph.** The graph also holds relations between contexts themselves: a Slack reply and the message it answers, a page and the pages it links to, a comment and its ticket. Given one context's id, [Connected Subgraph](/api-reference/v2/endpoint/subgraph) walks those links breadth-first and returns everything reachable with the relations among them. Reach for it when one query result is not enough and you need what surrounds it; it is also what the dashboard's **Subgraph** button opens. For the full field reference, see [Query](/essentials/v2/query#graph). @@ -189,7 +189,7 @@ Inject `llm_prompt` and the model can reason over the paths and cite them. See [ **Treating triplets as flat strings.** `source`, `relation` and `target` are objects with their own fields. Read them as structured data. -**Looking for forceful relations in `graph[]`.** Items you linked with `forceful_relations` come back in `forceful_relations[]`, with the `via` link that brought them in. +**Looking for forceful relations in `graph[]`.** Context you linked with `forceful_relations` comes back in `forceful_relations[]`, with the `via` link that brought them in. **Expecting relationships that do not exist.** If nothing in the graph connects the query and the retrieved chunks, `graph` is `[]`. diff --git a/essentials/v2/databases-and-collections.mdx b/essentials/v2/databases-and-collections.mdx index 9e5f63f4..117ece61 100644 --- a/essentials/v2/databases-and-collections.mdx +++ b/essentials/v2/databases-and-collections.mdx @@ -4,7 +4,7 @@ description: "How HydraDB scopes data using databases and collections, and how s --- - **Knowledge and memory (split databases) are deprecated.** Every database you create is unified, and you do not pass anything to get it. Send `context` items with [Ingest context](/essentials/v2/ingest) and read them back with [Query](/essentials/v2/query). + **Knowledge and memory (split databases) are deprecated.** Every database you create is unified, and you do not pass anything to get it. Send a `context` list with [Ingest context](/essentials/v2/ingest) and read them back with [Query](/essentials/v2/query). @@ -58,7 +58,7 @@ Use this when each user has private context, preferences, or conversation histor Typical flow: -- Write each user's items with `collection = user_id`. +- Write each user's context with `collection = user_id`. - Query that user's context with the same `collection`. - Keep shared context outside the user-specific scope. @@ -167,7 +167,7 @@ const client = new HydraDBClient({ }); // 1. Write a person's preference under their own collection. -// The SDK sends the item list in the `context` form field. +// The SDK sends the list in the `context` form field. await client.context.ingest({ database: "acme_corp", collection: "user_123", @@ -197,7 +197,7 @@ from hydra_db import HydraDB client = HydraDB(token=os.environ["HYDRA_DB_API_KEY"]) # 1. Write a person's preference under their own collection. -# The SDK sends the item list in the `context` form field. +# The SDK sends the list in the `context` form field. client.context.ingest( database="acme_corp", collection="user_123", @@ -310,7 +310,7 @@ In a JSON body each of `database`, `collection`, `tenant_id` and `sub_tenant_id` ## Related -- [Ingest context](/essentials/v2/ingest): writing items into a collection +- [Ingest context](/essentials/v2/ingest): writing context into a collection - [Query](/essentials/v2/query): how scoping is applied at query time - [How to Use API Results](/essentials/v2/api-results): merging query results into a prompt - [Create Database](/api-reference/v2/endpoint/tenants-overview): defining databases and their metadata schema diff --git a/essentials/v2/glossary.mdx b/essentials/v2/glossary.mdx index a1ea0f04..358ebb34 100644 --- a/essentials/v2/glossary.mdx +++ b/essentials/v2/glossary.mdx @@ -16,17 +16,17 @@ it and HydraDB uses the database's default collection. See [Databases and collections](/essentials/v2/databases-and-collections) for how scoping affects writes and reads. -## Context item +## Context One piece of context you ingest: a `text` or a `conversation`, identified by its -`context_id`. Items are sent in the `context` list of `POST /context/ingest` and come +`context_id`. Each is one entry in the `context` list of `POST /context/ingest` and comes back from `POST /query` as `chunks`, each carrying the `context_id` it came from. See [Ingest context](/essentials/v2/ingest). ## Forceful relations -Links you declare between context items at ingest, with an item's `forceful_relations` -field. When a query hits an item, HydraDB follows its declared links +Links you declare between contexts at ingest, with a context's `forceful_relations` +field. When a query hits a context, HydraDB follows its declared links (`follow_forceful_relations`, on by default; `thinking` mode only) and returns the linked chunks in the response's `forceful_relations[]`, each with the `via` link that brought it in, separately from the ranked `chunks` and the `graph` paths. See diff --git a/essentials/v2/graph-collections-byog.mdx b/essentials/v2/graph-collections-byog.mdx index b5d5416c..24f9e74e 100644 --- a/essentials/v2/graph-collections-byog.mdx +++ b/essentials/v2/graph-collections-byog.mdx @@ -10,7 +10,7 @@ existing property-graph workload (for example from Neo4j) who want to keep their Cypher and their data model as they are. - Cypher Graph Collections are separate from context items. The endpoints live + Cypher Graph Collections are separate from ingested context. The endpoints live under the `/byog` path, but they are not [Bring Your Own Graph](/essentials/v2/bring-your-own-graph), which attaches your own entities and relations to a context item at ingest with `graph_payload`. diff --git a/essentials/v2/ingest.mdx b/essentials/v2/ingest.mdx index 9f821a38..fc1344f5 100644 --- a/essentials/v2/ingest.mdx +++ b/essentials/v2/ingest.mdx @@ -1,6 +1,6 @@ --- title: "Ingest context" -description: "Send text and conversations to HydraDB as context items in one call, and confirm they are searchable." +description: "Send text and conversations to HydraDB as context in one call, and confirm they are searchable." --- Everything you put into HydraDB is a piece of **context**: a text, or a conversation. You send a list of them to one endpoint, [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context), and HydraDB chunks each one, embeds it, enriches it, extracts entities and relations into the [context graph](/essentials/v2/context-graphs), and makes it searchable through [`POST /query`](/essentials/v2/query). @@ -9,7 +9,7 @@ Everything you put into HydraDB is a piece of **context**: a text, or a conversa ## 1. Send context -`POST /context/ingest` takes a list called `context`. Each item is either a `text` or a `conversation`, never both. One request can carry items of both kinds. +`POST /context/ingest` takes a list called `context`. Each entry is either a `text` or a `conversation`, never both. One request can carry both kinds. ```bash cURL @@ -97,7 +97,7 @@ console.log(ingest.data.results.map((r) => r.id)); -**SDK users: `context` is a JSON string.** The SDKs send a multipart form rather than a JSON body, and the array goes in the `context` form field, which is why `context` is a JSON string there. The SDK methods also take `database`, `collection`, `upsert`, `enrich`, `instructions` and `graph_payload`, and `enrich` and `instructions` can also be set on each item. Both entry points run the same validation. Prefer the JSON body with `context` when you call the API directly. Keys inside each item stay `snake_case` in every language. +**SDK users: `context` is a JSON string.** The SDKs send a multipart form rather than a JSON body, and the array goes in the `context` form field, which is why `context` is a JSON string there. The SDK methods also take `database`, `collection`, `upsert`, `enrich`, `instructions` and `graph_payload`, and `enrich` and `instructions` can also be set on each context. Both entry points run the same validation. Prefer the JSON body with `context` when you call the API directly. Keys inside each context stay `snake_case` in every language. The response is `202 Accepted`: @@ -120,12 +120,12 @@ The response is `202 Accepted`: } ``` -- `message` starts with `Context queued for ingestion successfully` (`Context ingestion completed with some failures` when an item failed), followed by a reminder to poll status. -- `results[].id` is the item's `context_id`: the one you sent, or the generated one. Pass it to [`GET /context/status`](/api-reference/v2/endpoint/source-status). -- `results[].infer` mirrors the item's `enrich`. -- `results[].status` is `queued` or `failed`. A failed item carries `error` and `error_code`; the other items in the request are still queued. +- `message` starts with `Context queued for ingestion successfully` (`Context ingestion completed with some failures` when a context failed), followed by a reminder to poll status. +- `results[].id` is the context's `context_id`: the one you sent, or the generated one. Pass it to [`GET /context/status`](/api-reference/v2/endpoint/source-status). +- `results[].infer` mirrors the context's `enrich`. +- `results[].status` is `queued` or `failed`. A failed context carries `error` and `error_code`; the others in the request are still queued. -A `202` means the items were accepted and queued, not that they are searchable yet. See [Verify processing](#13-verify-processing). +A `202` means the contexts were accepted and queued, not that they are searchable yet. See [Verify processing](#13-verify-processing). --- @@ -135,49 +135,49 @@ A `202` means the items were accepted and queued, not that they are searchable y | --- | --- | | `database` | Required. The database to write to. `tenant_id` is its deprecated alias. | | `collection` | Optional. The collection to write to; the default collection when omitted. `sub_tenant_id` is its deprecated alias. | -| `context` | The list of items, at most 100. | -| `enrich` | Request-level default for every item's `enrich`. Default `true`. | -| `upsert` | Request-level default for every item's `upsert`. Default `true`. | -| `instructions` | Request-level default for every item's `instructions`. Default empty. | +| `context` | The list of contexts, at most 100. | +| `enrich` | Request-level default for every context's `enrich`. Default `true`. | +| `upsert` | Request-level default for every context's `upsert`. Default `true`. | +| `instructions` | Request-level default for every context's `instructions`. Default empty. | | `graph_payload` | Optional. A graph you built yourself, keyed by `context_id`. See [Bring Your Own Graph](#11-bring-your-own-graph). | -The three request-level defaults apply to any item that does not set the field itself, so one call can enrich some items and store others verbatim, or replace some items and append others. +The three request-level defaults apply to any context that does not set the field itself, so one call can enrich some contexts and store others verbatim, or replace some and append others. --- -## 3. Item fields +## 3. Context fields -Each item is exactly one of `text` or `conversation`. +Each entry is exactly one of `text` or `conversation`. | Field | Notes | | --- | --- | -| `context_id` | Your id for the item. Generated from the item's text and `title` when omitted, so two items with the same text and title and no id collide. Must not contain commas. | +| `context_id` | Your id for the context. Generated from its text and `title` when omitted, so two contexts with the same text and title and no id collide. Must not contain commas. | | `title` | Optional readable name. Searchable with `titles` on [query](/essentials/v2/query). At most 1,024 bytes. | -| `text` | Plain text. Shape A. See [Text items](#4-text-items). | -| `conversation` | A list of `{ role, content }` turns; roles are `user`, `assistant` and `system`. Shape B. See [Conversation items](#5-conversation-items). | -| `enrich` | Extract entities, relations and preferences from this item. Default: the request's `enrich`, else `true`. | -| `upsert` | Replace an existing item with the same `context_id`. Default: the request's `upsert`, else `true`. | -| `instructions` | Steer enrichment for this item. At most 4,000 characters. Default: the request's `instructions`. | -| `happened_at` | The date the item is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the item separately and returns that as `received_at` on query chunks. | +| `text` | Plain text. Shape A. See [Text context](#4-text-context). | +| `conversation` | A list of `{ role, content }` turns; roles are `user`, `assistant` and `system`. Shape B. See [Conversation context](#5-conversation-context). | +| `enrich` | Extract entities, relations and preferences from this context. Default: the request's `enrich`, else `true`. | +| `upsert` | Replace an existing context with the same `context_id`. Default: the request's `upsert`, else `true`. | +| `instructions` | Steer enrichment for this context. At most 4,000 characters. Default: the request's `instructions`. | +| `happened_at` | The date the context is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the context separately and returns that as `received_at` on query chunks. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. See [Attributes](/essentials/v2/attributes). | | `custom_attributes` | Free-form fields. Not filterable with `attributes`. | -| `forceful_relations` | Relations you declare to other items: `{ "context_ids": ["chat-w1"], "properties": {} }`, where `context_ids` are the `context_id`s of the related items. See [Declared relations](#10-declared-relations). | -| `acl` | Principals allowed to retrieve the item, such as `user_email:a@x.com` or `domain:acme.com`. Omit for unrestricted, `[]` for nobody. A malformed principal is a `400`. See [Restricting an item](#9-restricting-an-item). | -| `user_name` | The speaker for the item: the author of a text item, or the person in a conversation's `user` turns. Default `"User"`. | +| `forceful_relations` | Relations you declare to other contexts: `{ "context_ids": ["chat-w1"], "properties": {} }`, where `context_ids` are the `context_id`s of the related contexts. See [Declared relations](#10-declared-relations). | +| `acl` | Principals allowed to retrieve the context, such as `user_email:a@x.com` or `domain:acme.com`. Omit for unrestricted, `[]` for nobody. A malformed principal is a `400`. See [Restricting a context](#9-restricting-a-context). | +| `user_name` | The speaker for the context: the author of a text context, or the person in a conversation's `user` turns. Default `"User"`. | ### Limits and unrecognised fields -- At most **100 items** per request, **1 MiB** of text per item, and **8 MiB** of text per request. +- At most **100 contexts** per request, **1 MiB** of text per context, and **8 MiB** of text per request. - The whole request body is capped at **16 MiB**: the JSON body, or the `context` form field when an SDK sends a multipart form. A larger one is refused with `413`. -- `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each item. -- A validation error names the item it refers to as `context[N]`. -- An unrecognised field is a `400`, on the request, on an item, on a conversation turn or inside `forceful_relations`. The error names the field and lists the accepted ones. +- `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each context. +- A validation error names the context it refers to as `context[N]`. +- An unrecognised field is a `400`, on the request, on a context, on a conversation turn or inside `forceful_relations`. The error names the field and lists the accepted ones. --- -## 4. Text items +## 4. Text context -A text item is a document, a note, a policy, an agent log line: anything you already have as a string. +A text context is a document, a note, a policy, an agent log line: anything you already have as a string. ```json { @@ -188,16 +188,16 @@ A text item is a document, a note, a policy, an agent log line: anything you alr } ``` -- Set `title` so the item has a readable name and so `titles` filters can find it. +- Set `title` so the context has a readable name and so `titles` filters can find it. - Set `user_name` when the text has an author the graph should attribute facts to. -### Turning files into items +### Turning files into context -`POST /context/ingest` takes text, not files. Extract the text in your application (a PDF parser, a DOCX reader, your CMS export) and send it as `text`, one item per document. For sources that live in tools like Slack, Notion or Google Drive, use a [connector](/essentials/v2/connectors) instead. +`POST /context/ingest` takes text, not files. Extract the text in your application (a PDF parser, a DOCX reader, your CMS export) and send it as `text`, one context per document. For sources that live in tools like Slack, Notion or Google Drive, use a [connector](/essentials/v2/connectors) instead. --- -## 5. Conversation items +## 5. Conversation context ```json { @@ -215,22 +215,22 @@ A text item is a document, a note, a policy, an agent log line: anything you alr This is the message list you already build for OpenAI or Anthropic, so you can usually pass it straight through. - Roles are `user`, `assistant` and `system`. An unknown role is a `400`. -- **`system` turns are context only.** They are never stored as facts. When neither the item nor the request sets `instructions`, they become the item's instructions and are held to the same 4,000-character limit; otherwise they are dropped. A conversation of only `system` turns is a `400`. +- **`system` turns are context only.** They are never stored as facts. When neither the context nor the request sets `instructions`, they become the context's instructions and are held to the same 4,000-character limit; otherwise they are dropped. A conversation of only `system` turns is a `400`. - **Consecutive turns with the same role are accepted** and joined. -- **The speaker is the item's `user_name`.** A turn carries only `role` and `content`; any other key on a turn is a `400`. +- **The speaker is the context's `user_name`.** A turn carries only `role` and `content`; any other key on a turn is a `400`. - An empty list, or a turn with empty `content`, is a `400`. --- ## 6. Enrichment and instructions -With `enrich: true` (the default), HydraDB reads each item and extracts entities, the relations between them, and preferences, and writes them into the context graph. That is what lets a query about "Alex" reach a conversation where Alex never used the word the query used. +With `enrich: true` (the default), HydraDB reads each context and extracts entities, the relations between them, and preferences, and writes them into the context graph. That is what lets a query about "Alex" reach a conversation where Alex never used the word the query used. -The enriched output is stored **separately** from the item's own text. On [query](/essentials/v2/query) it comes back as `chunks[].enrichment`, next to the chunk's verbatim `content`; the two are never concatenated. +The enriched output is stored **separately** from the context's own text. On [query](/essentials/v2/query) it comes back as `chunks[].enrichment`, next to the chunk's verbatim `content`; the two are never concatenated. -Turn it off with `enrich: false` when the item is already exactly what you want stored and you only need it searchable, for example a raw transcript you keep for reference. +Turn it off with `enrich: false` when the context is already exactly what you want stored and you only need it searchable, for example a raw transcript you keep for reference. -Use `instructions` to steer extraction. Set it on the request to apply it to every item, or on one item to override it there: +Use `instructions` to steer extraction. Set it on the request to apply it to every context, or on one context to override it there: ```json { @@ -255,17 +255,17 @@ Use `instructions` to steer extraction. Set it on the request to apply it to eve } ``` -`attributes` are the fields you declared in the database's `database_metadata_schema`, and you can filter on them at query time with `attributes` on [`POST /query`](/essentials/v2/query#2-request). `custom_attributes` are free-form: they are stored with the item and cannot be filtered with `attributes`. Neither is returned on query chunks; read them from the item's row in [`POST /context/list`](/api-reference/v2/endpoint/list-documents). See [Attributes](/essentials/v2/attributes). +`attributes` are the fields you declared in the database's `database_metadata_schema`, and you can filter on them at query time with `attributes` on [`POST /query`](/essentials/v2/query#2-request). `custom_attributes` are free-form: they are stored with the context and cannot be filtered with `attributes`. Neither is returned on query chunks; read them from the context's row in [`POST /context/list`](/api-reference/v2/endpoint/list-documents). See [Attributes](/essentials/v2/attributes). --- ## 8. Time -`happened_at` is when the item is about: the meeting date, the decision date, the day a preference was stated. HydraDB records when it received the item separately, and query chunks return that receipt time as `received_at`. Set `happened_at` whenever it differs from ingest time, so recency and temporal reasoning at query time use the right date. +`happened_at` is when the context is about: the meeting date, the decision date, the day a preference was stated. HydraDB records when it received the context separately, and query chunks return that receipt time as `received_at`. Set `happened_at` whenever it differs from ingest time, so recency and temporal reasoning at query time use the right date. --- -## 9. Restricting an item +## 9. Restricting a context ```json { @@ -275,13 +275,13 @@ Use `instructions` to steer extraction. Set it on the request to apply it to eve } ``` -Omit `acl` and the item is unrestricted. Send `[]` and nobody can retrieve it. A malformed principal rejects the whole request with `400`, so an item is never stored unprotected by accident. See [Access control](/essentials/v2/access-control). +Omit `acl` and the context is unrestricted. Send `[]` and nobody can retrieve it. A malformed principal rejects the whole request with `400`, so a context is never stored unprotected by accident. See [Access control](/essentials/v2/access-control). --- ## 10. Declared relations -Any item, text or conversation, can declare which other items it relates to: +Any context, text or conversation, can declare which other contexts it relates to: ```json { @@ -291,13 +291,13 @@ Any item, text or conversation, can declare which other items it relates to: } ``` -`context_ids` are the `context_id`s of the related items. `properties` is optional: a flat map of string, number or boolean values, at most 1 KiB as compact JSON, stored on every edge the item declares. The keys `id`, `created_at`, `relation_type`, `tenant_id` and `sub_tenant_id` are reserved and are a `400`. At query time, in `thinking` mode and with `follow_forceful_relations` on (the default), a hit on one item pulls its declared relations into the response's `forceful_relations[]`, each with the `via` link that brought it in. See [Query](/essentials/v2/query#forceful_relations). +`context_ids` are the `context_id`s of the related contexts. `properties` is optional: a flat map of string, number or boolean values, at most 1 KiB as compact JSON, stored on every edge the context declares. The keys `id`, `created_at`, `relation_type`, `tenant_id` and `sub_tenant_id` are reserved and are a `400`. At query time, in `thinking` mode and with `follow_forceful_relations` on (the default), a hit on one context pulls its declared relations into the response's `forceful_relations[]`, each with the `via` link that brought it in. See [Query](/essentials/v2/query#forceful_relations). --- ## 11. Bring Your Own Graph -Skip extraction for an item and supply its entities and relations yourself with `graph_payload`, a map keyed by `context_id`: +Skip extraction for a context and supply its entities and relations yourself with `graph_payload`, a map keyed by `context_id`: ```json { @@ -319,23 +319,23 @@ Skip extraction for an item and supply its entities and relations yourself with } ``` -Every key in `graph_payload` must match the `context_id` of an item in the same request; a key that matches nothing is a `400`, so a typo cannot silently drop a graph. A keyed item is still chunked and embedded, so it stays searchable. The entity and relation shapes, caps and replace semantics are on [Bring Your Own Graph](/essentials/v2/bring-your-own-graph). +Every key in `graph_payload` must match the `context_id` of a context in the same request; a key that matches nothing is a `400`, so a typo cannot silently drop a graph. A keyed context is still chunked and embedded, so it stays searchable. The entity and relation shapes, caps and replace semantics are on [Bring Your Own Graph](/essentials/v2/bring-your-own-graph). --- ## 12. IDs and replacement -- `context_id` is yours. Reuse it to replace an item. +- `context_id` is yours. Reuse it to replace a context. - `upsert: true` (the default) **replaces**: re-ingesting a `context_id` deletes everything derived from the previous version (its chunks and its graph contribution) before writing the new one. It does not merge. -- `upsert` is per item, with the request value as the default, so one call can replace some items and append others. -- When you omit `context_id`, the id is generated from the item's text and `title`. Give repeated text either a `context_id` or a distinct `title`, or the second item replaces the first. +- `upsert` is set per context, with the request value as the default, so one call can replace some contexts and append others. +- When you omit `context_id`, the id is generated from the context's text and `title`. Give repeated text either a `context_id` or a distinct `title`, or the second context replaces the first. - `context_id` must not contain commas. --- ## 13. Verify processing -Ingestion is asynchronous. Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) with the `results[].id` values from the ingest response until each item reaches `completed` or `errored`. +Ingestion is asynchronous. Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) with the `results[].id` values from the ingest response until each context reaches `completed` or `errored`. ```python Python SDK @@ -388,21 +388,21 @@ To be notified instead of polling, register a [webhook](/essentials/v2/webhooks) ## 14. Other ways context arrives -[Connectors](/essentials/v2/connectors) sync Slack, Notion, Google Drive, GitHub and other tools into a database on a schedule. Synced context lands in the same database as your items and is queried together with them. +[Connectors](/essentials/v2/connectors) sync Slack, Notion, Google Drive, GitHub and other tools into a database on a schedule. Synced context lands in the same database as your own context and is queried together with it. --- ## 15. Common mistakes - -An item carries exactly one of `text` or `conversation`. Sending both, or neither, is a `400`. Split them into two items. + +A context carries exactly one of `text` or `conversation`. Sending both, or neither, is a `400`. Split them into two contexts. -Ingest takes text only. Extract the text from the file in your application and send it as a `text` item. See [Turning files into items](#turning-files-into-items). +Ingest takes text only. Extract the text from the file in your application and send it as a `text` context. See [Turning files into context](#turning-files-into-context). - -An unrecognised field is a `400` that names it and lists the accepted fields; it is never ignored or guessed at. Use the names in [Item fields](#3-item-fields). + +An unrecognised field is a `400` that names it and lists the accepted fields; it is never ignored or guessed at. Use the names in [Context fields](#3-context-fields). Only `user`, `assistant` and `system` are accepted. Map roles like `tool` or `human` before sending. @@ -410,11 +410,11 @@ Only `user`, `assistant` and `system` are accepted. Map roles like `tool` or `hu `attributes` cannot filter on `custom_attributes`. Declare the field in `database_metadata_schema` and send it in `attributes` instead. - -Every key must equal the `context_id` of an item in the same request. Anything else is a `400`. + +Every key must equal the `context_id` of a context in the same request. Anything else is a `400`. -A `202` means queued. Poll status until `graph_creation` or `completed` before expecting the item in results. +A `202` means queued. Poll status until `graph_creation` or `completed` before expecting the context in results. diff --git a/essentials/v2/query.mdx b/essentials/v2/query.mdx index dce84f4b..a7444a95 100644 --- a/essentials/v2/query.mdx +++ b/essentials/v2/query.mdx @@ -3,7 +3,7 @@ title: "Query" description: "One call to POST /query returns ranked chunks, graph paths, forceful relations and a prompt-ready string. Every request and response field." --- -Query turns stored context into the *right* context for one question. One endpoint, [`POST /query`](/api-reference/v2/endpoint/query), searches everything in the collections you name: your text and conversation items, connector content, and the [context graph](/essentials/v2/context-graphs) built from all of it. Three signals drive relevance: dense-vector similarity, BM25 keyword matching and graph traversal. +Query turns stored context into the *right* context for one question. One endpoint, [`POST /query`](/api-reference/v2/endpoint/query), searches everything in the collections you name: your text and conversation context, connector content, and the [context graph](/essentials/v2/context-graphs) built from all of it. Three signals drive relevance: dense-vector similarity, BM25 keyword matching and graph traversal. The response is one shape with four keys. @@ -173,9 +173,9 @@ See [When to use each](/essentials/v2/databases-and-collections#2-when-to-use-ea | `database` | string | Required. The database to search. | | `collection` | string | Single-scope selector. Send one collection to search only it; the default collection when neither this nor `collections` is sent. | | `collections` | `string[]` or `{ [collection]: positive number (max one decimal place) }` | Preferred selector. A list fans out with equal normalized weights; an object applies relative ranking weights. Maximum 100 collections. `max_results` caps the merged result. | -| `ids` | `string[]` | Restrict retrieval to these `context_id`s. | -| `titles` | `string[]` | Restrict retrieval to items with one of these exact titles (case-insensitive, ORed). Intersected with `ids` when both are sent. | -| `acl` | `string[]` | Query on behalf of an identity: results are restricted to items that identity may retrieve. Omitted, empty or `["*"]` disables filtering. See [Access control](/essentials/v2/access-control). | +| `ids` | `string[]` | Restrict retrieval to these `context_id`s, at most 200. | +| `titles` | `string[]` | Restrict retrieval to context with one of these exact titles (case-insensitive, ORed), at most 500. Intersected with `ids` when both are sent. | +| `acl` | `string[]` | Query on behalf of an identity: results are restricted to context that identity may retrieve. Omitted, empty or `["*"]` disables filtering. See [Access control](/essentials/v2/access-control). | | `attributes` | object | Filter on declared attributes with operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$exists`. Applies to chunks, forceful relations and graph paths alike. See [Attributes](/essentials/v2/attributes). | ```json @@ -210,7 +210,7 @@ See [When to use each](/essentials/v2/databases-and-collections#2-when-to-use-ea | Parameter | Type / values | Purpose | | --- | --- | --- | | `graph_context` | boolean | Default `true`. Include graph paths in `graph[]`. Set `false` for chunks only; `graph` is then `[]`. | -| `follow_forceful_relations` | boolean | Default `true`. Pull in the items each hit declared with `forceful_relations` at ingest, into `forceful_relations[]`. Declared relations are followed only in `thinking` mode. Set `false` for `forceful_relations: []`. `query_forceful_relations` is the deprecated alias. | +| `follow_forceful_relations` | boolean | Default `true`. Pull in the context each hit declared with `forceful_relations` at ingest, into `forceful_relations[]`. Declared relations are followed only in `thinking` mode. Set `false` for `forceful_relations: []`. `query_forceful_relations` is the deprecated alias. | ### Time @@ -230,21 +230,21 @@ See [When to use each](/essentials/v2/databases-and-collections#2-when-to-use-ea ### `chunks[]` -The matched pieces of your items, ranked. Preserve the order. +The matched pieces of your context, ranked. Preserve the order. | Field | Type | Meaning | | --- | --- | --- | | `chunk_id` | string | The chunk's id. Referenced from `graph[].triplets[].relation.chunk_id`. | -| `context_id` | string | The item this chunk came from. Pass it to `GET /context/inspect`. | +| `context_id` | string | The context this chunk came from. Pass it to `GET /context/inspect`. | | `score` | number | Relevance. Always present. | | `content` | string | The chunk's own text, verbatim. Enrichment is not concatenated into it. | | `enrichment` | string | What enrichment extracted from this chunk: the extracted statement (a preference, a fact). Omitted when enrichment extracted nothing. | | `enrichment_kind` | string | An optional label; omitted when none was set. | -| `received_at` | string | When HydraDB received the item this chunk came from, as an RFC 3339 timestamp (for example `2026-07-02T09:14:05Z`). This is the ingest time, not the item's `happened_at`, which is not returned here. Omitted when no receipt time is recorded for the chunk, as on older items; it is never sent empty. | +| `received_at` | string | When HydraDB received the context this chunk came from, as an RFC 3339 timestamp (for example `2026-07-02T09:14:05Z`). This is the ingest time, not its `happened_at`, which is not returned here. Omitted when no receipt time is recorded for the chunk, as on context ingested before it existed; it is never sent empty. | | `temporal` | array | Present only when the query engaged temporal reasoning. Each entry is `{ content, start_date, end_date }`: `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` (only the dated sides are printed), and either date may be `null`. | -**Chunks carry almost nothing about their source.** A chunk has no title, url, collection or attributes; the one source detail it carries is `received_at`. `llm_prompt` prints the title, collection, type, last-updated date and url for the model. To show an item's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. +**Chunks carry almost nothing about their source.** A chunk has no title, url, collection or attributes; the one source detail it carries is `received_at`. `llm_prompt` prints the title, collection, type, last-updated date and url for the model. To show a context's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. ### `graph[]` @@ -351,7 +351,7 @@ The sections, in order: | `## Code search` | The repository code-search answer, one `### repository` block each, with its status. Prompt only. | | `## Sources` | Each context once, in order of first appearance: title, type, id, url and last-updated date. Only web (`http` or `https`) links are printed; a storage location such as `s3://...` never is. | -`**Type:**` is what the item is: the connector's word for it (a Slack `message`, a Jira `ticket`) when a connector set one, otherwise its source type, such as `file`. A field with no value is left out of its line. +`**Type:**` is what the context is: the connector's word for it (a Slack `message`, a Jira `ticket`) when a connector set one, otherwise its source type, such as `file`. A field with no value is left out of its line. | Label | Refers to | | --- | --- | @@ -466,7 +466,7 @@ Most of the time the defaults are right. When they are not, here is where to sta | --- | --- | --- | | `graph` is `[]` | `graph_context: false`, or nothing connects the results | Leave `graph_context` on; `mode: "thinking"` explores more of the graph. An empty array is normal when there is nothing to return. | | `forceful_relations` is `[]` | Nothing in the hits declared `forceful_relations`, `follow_forceful_relations: false`, or the query ran in `fast` mode | Declare relations at ingest, leave the flag on, and use `mode: "thinking"`. | -| Recent items do not appear | Indexing not finished | Poll `GET /context/status?ids=...&database=...`; chunks are invisible until processing reaches at least `graph_creation`. | +| Recent context does not appear | Indexing not finished | Poll `GET /context/status?ids=...&database=...`; chunks are invisible until processing reaches at least `graph_creation`. | | `attributes` does not narrow results | The key is not declared in `database_metadata_schema`, or the value does not match | Declare the field and send it in `attributes` at ingest; filter with an operator such as `$eq`. `attributes` cannot filter on `custom_attributes`. | | Chunk has no title or url | Chunks carry no title, url, collection or attributes by design | Read them from `llm_prompt`, or call `POST /context/list` with `ids: [""]`. | | `operator: "phrase"` ignored | `query_by` is not `"text"` | `operator` only applies to BM25 text query. | @@ -478,5 +478,5 @@ Most of the time the defaults are right. When they are not, here is where to sta - [How to Use API Results](/essentials/v2/api-results): injecting `llm_prompt` and reading the four keys - [Context graphs](/essentials/v2/context-graphs): how `graph[]` is built - [Attributes](/essentials/v2/attributes): designing filterable fields -- [Ingest context](/essentials/v2/ingest): the items a query searches +- [Ingest context](/essentials/v2/ingest): the context a query searches - [Query API reference](/api-reference/v2/endpoint/query): full parameter and response schema diff --git a/essentials/v2/webhooks.mdx b/essentials/v2/webhooks.mdx index 0a1fa90f..a1c47d4b 100644 --- a/essentials/v2/webhooks.mdx +++ b/essentials/v2/webhooks.mdx @@ -314,7 +314,7 @@ For failed indexing, the payload can include `error_code` and `error_message`: | `event` | Event type. Currently `indexing.status_changed`. | | `delivery_id` | Stable ID for this event. Store it to deduplicate retries. | | `id` | The item's `context_id`: the one you supplied at ingestion, or the generated one. For connector-synced content, the connector item's id. | -| `database` | The name of the database you ingested into: the value you sent as `database` (or `tenant_id`) on the ingest request. Empty only for items ingested before this field existed. | +| `database` | The name of the database you ingested into: the value you sent as `database` (or `tenant_id`) on the ingest request. Empty only for context ingested before this field existed. | | `collection` | Collection scope for the indexed item. | | `status` | Terminal indexing status. Usually `completed` or `errored`. | | `timestamp` | Time the webhook payload was created. | @@ -339,7 +339,7 @@ matching on it keep working unchanged. `sub_tenant_id` remains an exact alias fo -`database` is empty only for items ingested before this field existed. Read +`database` is empty only for context ingested before this field existed. Read `tenant_id` if you need a scope that is always set. diff --git a/get-started/v2/core-concepts.mdx b/get-started/v2/core-concepts.mdx index 3852113c..71b79e9d 100644 --- a/get-started/v2/core-concepts.mdx +++ b/get-started/v2/core-concepts.mdx @@ -1,12 +1,12 @@ --- title: "Core Concepts" -description: "A tour of the primitives that make HydraDB: databases and collections, items, query, attributes, the context graph and access control." +description: "A tour of the primitives that make HydraDB: databases and collections, context, query, attributes, the context graph and access control." --- | Primitive | What it is | Deep dive | | --- | --- | --- | | **Databases and collections** | Isolated databases, partitioned into collections per user, team or project | [Databases and collections](/essentials/v2/databases-and-collections) | -| **Context (items)** | Text and conversations you ingest, one item at a time | [Ingest context](/essentials/v2/ingest) | +| **Context** | Text and conversations you ingest, as a `context` list | [Ingest context](/essentials/v2/ingest) | | **Query** | One endpoint that reads context back, personalized with weighted collections | [Query](/essentials/v2/query) | | **Attributes** | Declared fields you filter on, for deterministic retrieval | [Attributes](/essentials/v2/attributes) | | **Context graph** | Entities, relations and decisions extracted from everything you ingest | [Context graphs](/essentials/v2/context-graphs) | @@ -24,13 +24,13 @@ One database holds all the context your AI needs, and it holds three kinds: These are kinds of content, not a setting you pass: you send all of them as text or conversations. -You partition the database into **collections**, typically one per person plus one or more shared ones. You ingest everything as **items**. You read it back with one **query** that can weigh a person's collection above the shared ones, so answers are grounded in company knowledge and personalized for the person asking. +You partition the database into **collections**, typically one per person plus one or more shared ones. You ingest everything as **context**. You read it back with one **query** that can weigh a person's collection above the shared ones, so answers are grounded in company knowledge and personalized for the person asking. --- -## Items +## Context -An item is either plain `text` or a `conversation`: a document, a policy, a support chat, an agent's log of what it did. You send items to [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context), and HydraDB chunks them, embeds them, and extracts entities and relations into the context graph. +Each context is either plain `text` or a `conversation`: a document, a policy, a support chat, an agent's log of what it did. You send it in the `context` list to [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context), and HydraDB chunks them, embeds them, and extracts entities and relations into the context graph. ```json { @@ -50,7 +50,7 @@ An item is either plain `text` or a `conversation`: a document, a policy, a supp } ``` -Enrichment is on by default (`enrich: true`): send raw conversations and logs, and HydraDB extracts the preferences and facts in them, stored separately from the item's own text. Turn it off for items you want stored exactly as sent. The SDKs send the same list in the `context` form field. +Enrichment is on by default (`enrich: true`): send raw conversations and logs, and HydraDB extracts the preferences and facts in them, stored separately from the context's own text. Turn it off for context you want stored exactly as sent. The SDKs send the same list in the `context` form field. Read more: [Ingest context](/essentials/v2/ingest) @@ -74,7 +74,7 @@ Personalize by querying several collections with weights: Tune it with `query_by` (`hybrid` or `text`), `mode` (`auto`, `fast` or `thinking`) and `graph_context`. -The response is four keys: `chunks` (ranked matches with `content`, `score` and `enrichment`), `graph` (relation paths, each with a `path_summary`), `forceful_relations` (items you linked at ingest) and `llm_prompt`, a server-built string with citation labels that you inject into your model call as is. +The response is four keys: `chunks` (ranked matches with `content`, `score` and `enrichment`), `graph` (relation paths, each with a `path_summary`), `forceful_relations` (context you linked at ingest) and `llm_prompt`, a server-built string with citation labels that you inject into your model call as is. Read more: [Query](/essentials/v2/query) @@ -127,7 +127,7 @@ Read more: [Attributes](/essentials/v2/attributes) ## Context graph -As items are enriched, HydraDB builds a graph of the entities they mention and the relations between them. Query results include `graph[]`: paths of relations that connect what you asked about to what is relevant, including decisions and who made them, each summarized in one sentence. That is how an answer reaches context that shares no words with the query. +As context is enriched, HydraDB builds a graph of the entities they mention and the relations between them. Query results include `graph[]`: paths of relations that connect what you asked about to what is relevant, including decisions and who made them, each summarized in one sentence. That is how an answer reaches context that shares no words with the query. Read more: [Context graphs](/essentials/v2/context-graphs) @@ -143,6 +143,6 @@ Read more: [Access control](/essentials/v2/access-control) ## What's next -- [Quickstart](/get-started/v2/quickstart): create a database, ingest two items and query them in five minutes +- [Quickstart](/get-started/v2/quickstart): create a database, ingest two contexts and query them in five minutes - [Architecture](/essentials/v2/architecture): how the graph, vector store and ranking layers fit together -- [Ingest context](/essentials/v2/ingest): every item field +- [Ingest context](/essentials/v2/ingest): every context field diff --git a/get-started/v2/introduction.mdx b/get-started/v2/introduction.mdx index 555f5d3d..d29406fb 100644 --- a/get-started/v2/introduction.mdx +++ b/get-started/v2/introduction.mdx @@ -11,7 +11,7 @@ HydraDB is a unified context substrate for your AI. The brain behind your AI. On - **Business knowledge.** What your company knows: documents, policies, and the tools you connect. - **Decision traces.** What your agents and teams decided, and why. -You ingest it as items into one database and ask one query. HydraDB builds a context graph across all three and returns useful context, personalized for each user. +You ingest it as context into one database and ask one query. HydraDB builds a context graph across all three and returns useful context, personalized for each user. ## The problem we're solving @@ -70,4 +70,4 @@ For enterprise onboarding, contact [founders@hydradb.com](mailto:founders@hydrad ## For AI agents -For AI coding agents and IDE assistants, use the [HydraDB Agent Integration Guide](/AGENTS) and the [v2 OpenAPI spec](/api-reference/v2/openapi.json). Ingest `context` items and query them with `POST /query`; the query returns `llm_prompt`, ready to inject. \ No newline at end of file +For AI coding agents and IDE assistants, use the [HydraDB Agent Integration Guide](/AGENTS) and the [v2 OpenAPI spec](/api-reference/v2/openapi.json). Ingest a `context` list and query it with `POST /query`; the query returns `llm_prompt`, ready to inject. \ No newline at end of file diff --git a/get-started/v2/quickstart.mdx b/get-started/v2/quickstart.mdx index 2aa97fed..3b6d4b49 100644 --- a/get-started/v2/quickstart.mdx +++ b/get-started/v2/quickstart.mdx @@ -3,7 +3,7 @@ title: "Quickstart" description: "Create a database, ingest a document and a conversation, and run your first query in five minutes." --- -This guide walks through the full HydraDB loop: create a database, ingest two items, wait for indexing, and run a query, using the [API](/api-reference/v2). By the end you have a working personalized-RAG flow you can plug into your own LLM prompt. +This guide walks through the full HydraDB loop: create a database, ingest two contexts, wait for indexing, and run a query, using the [API](/api-reference/v2). By the end you have a working personalized-RAG flow you can plug into your own LLM prompt. If you are new to HydraDB, [Core Concepts](/get-started/v2/core-concepts) is a useful 5-minute primer first. @@ -60,7 +60,7 @@ while True: time.sleep(5) # 3. Ingest a policy into the shared collection and a conversation into Alex's. -# The SDK sends the item list in the `context` form field. +# The SDK sends the list in the `context` form field. client.context.ingest( database=database, collection="company", @@ -84,7 +84,7 @@ client.context.ingest( }]), ) -# 4. Wait until both items are indexed. +# 4. Wait until both contexts are indexed. pending = {"company": ["refund-policy"], "user_alex": ["chat-alex-001"]} while pending: for collection, ids in list(pending.items()): @@ -124,7 +124,7 @@ while (true) { } // 3. Ingest a policy into the shared collection and a conversation into Alex's. -// The SDK sends the item list in the `context` form field. +// The SDK sends the list in the `context` form field. await client.context.ingest({ database, collection: "company", @@ -148,7 +148,7 @@ await client.context.ingest({ }]), }); -// 4. Wait until both items are indexed. +// 4. Wait until both contexts are indexed. const pending = new Map([["company", "refund-policy"], ["user_alex", "chat-alex-001"]]); while (pending.size > 0) { for (const [collection, id] of pending) { @@ -217,7 +217,7 @@ curl -s -X POST "$API/context/ingest" "${AUTH[@]}" \ }] }" -# 4. Wait until both items are indexed. +# 4. Wait until both contexts are indexed. for pair in "company:refund-policy" "user_alex:chat-alex-001"; do COLLECTION="${pair%%:*}"; ID="${pair#*:}" while true; do @@ -243,7 +243,7 @@ curl -s -X POST "$API/query" "${AUTH[@]}" \ ``` -The response has four keys. `chunks` are the pieces of your items that matched, ranked, each with a `score` and its `context_id`. `graph` is the list of relation paths HydraDB found between them, such as Alex and their preference for short answers, each with a one-sentence `path_summary`. `forceful_relations` holds items you linked at ingest (none here). `llm_prompt` is all of that as one string with citation labels, ready to drop into your model call: +The response has four keys. `chunks` are the pieces of your context that matched, ranked, each with a `score` and its `context_id`. `graph` is the list of relation paths HydraDB found between them, such as Alex and their preference for short answers, each with a one-sentence `path_summary`. `forceful_relations` holds context you linked at ingest (none here). `llm_prompt` is all of that as one string with citation labels, ready to drop into your model call: ```python messages = [{"role": "system", "content": results.data.llm_prompt}, @@ -258,7 +258,7 @@ You have built the full retrieval loop: create an isolated database, ingest cont ```mermaid flowchart LR - A([1. Create Database]) --> B([2. Ingest Items]) + A([1. Create Database]) --> B([2. Ingest Context]) B --> C([3. Verify Processing]) C --> D([4. Query Context]) D --> E([5. Pass to LLM]) @@ -270,7 +270,7 @@ flowchart LR style E fill:#0f172a,stroke:#334155,stroke-width:2px,color:#f8fafc,stroke-linecap:round ``` -Creating a database and indexing are **asynchronous**: HydraDB provisions infrastructure and indexes your content in the background, so each is followed by a short polling loop. The ingest call returns `202` as soon as the items are queued, and querying runs in real time. One ingest request takes up to 100 items; send more requests for more. +Creating a database and indexing are **asynchronous**: HydraDB provisions infrastructure and indexes your content in the background, so each is followed by a short polling loop. The ingest call returns `202` as soon as the contexts are queued, and querying runs in real time. One ingest request takes up to 100 contexts in its `context` list; send more requests for more. --- @@ -278,7 +278,7 @@ Creating a database and indexing are **asynchronous**: HydraDB provisions infras | If you want to... | Read... | |---|---| -| See every item field, conversations and enrichment | [Ingest context](/essentials/v2/ingest) | +| See every context field, conversations and enrichment | [Ingest context](/essentials/v2/ingest) | | Read every field of the query response | [Query](/essentials/v2/query) | | Filter on declared fields | [Attributes](/essentials/v2/attributes) | | Scope data per user or workspace | [Databases and collections](/essentials/v2/databases-and-collections) | diff --git a/plugins/claude-code.mdx b/plugins/claude-code.mdx index 4aab1e4c..5fc438eb 100644 --- a/plugins/claude-code.mdx +++ b/plugins/claude-code.mdx @@ -97,7 +97,7 @@ Once configured, the plugin runs in the background: it syncs workspace docs on s The plugin talks to your database through two endpoints. - **Recall.** Before each prompt, it sends the prompt text to `POST /query`, with `mode` from `recallMode`, `graph_context` from `graphContext`, `follow_forceful_relations` from `followForcefulRelations`, and `max_results` set to `maxMemoryResults + maxKnowledgeResults` (10 by default). It injects the server-built `llm_prompt` verbatim, and whole, inside a `` block: ranked results, forceful relations, related facts from the context graph, temporal facts and sources, labelled `[1]`, `[R1]` and `[P1]` for citation. -- **Capture.** Conversations, notes and workspace docs are sent to `POST /context/ingest` as `context` items with enrichment on: +- **Capture.** Conversations, notes and workspace docs are sent to `POST /context/ingest` in the `context` list with enrichment on: - `turn` capture sends each exchange as a `conversation` item, with your turns named after `userName` when it is set. - `session-upsert` capture keeps one `text` item per session, holding the session transcript, and replaces it after each response. - `/hydradb:ingest --note` sends the note as a `text` item. @@ -168,7 +168,7 @@ The earlier names `/hydradb:status`, `/hydradb:search`, `/hydradb:remember`, `/h | `captureMode` | `session-upsert` | How conversations are saved (see [Modes](#modes)) | | `recallMode` | `fast` | Recall depth: `fast` or `thinking`, sent as the query `mode` | | `graphContext` | `true` | Include related facts from the context graph in recalled context | -| `followForcefulRelations` | `true` | Follow the relations declared at ingest, so recall also returns the linked items under Forceful relations. The server follows them in `thinking` mode. Env var: `HYDRADB_FOLLOW_FORCEFUL_RELATIONS` | +| `followForcefulRelations` | `true` | Follow the relations declared at ingest, so recall also returns the linked context under Forceful relations. The server follows them in `thinking` mode. Env var: `HYDRADB_FOLLOW_FORCEFUL_RELATIONS` | ### Limits diff --git a/plugins/cli.mdx b/plugins/cli.mdx index db1fa75d..099dbfcb 100644 --- a/plugins/cli.mdx +++ b/plugins/cli.mdx @@ -166,11 +166,11 @@ has run. |---|---| | `hydradb query QUERY` | Query the database: the single retrieval entry point | | `hydradb ingest` | Ingest one text or conversation item | -| `hydradb list` | List stored items | +| `hydradb list` | List stored context | | `hydradb inspect ID` | Fetch an item's content by ID | -| `hydradb delete IDS...` | Delete items by ID | +| `hydradb delete IDS...` | Delete context by ID | | `hydradb relations ID` | Explore context-graph relations for an item | -| `hydradb verify IDS...` | Check per-item ingestion status | +| `hydradb verify IDS...` | Check ingestion status per ID | #### Ingesting @@ -222,7 +222,7 @@ cat notes.txt | hydradb ingest --title "Meeting notes" --database my-db The command prints the queued item's context ID. Pass it to `hydradb verify` to watch indexing; an item is searchable once it has finished. See -[Ingest](/essentials/v2/ingest) for every item field. +[Ingest](/essentials/v2/ingest) for every context field. #### Querying @@ -236,7 +236,7 @@ hydradb query "What IDE does the user prefer?" --llm --database my-db # Deterministic keyword search hydradb query "PostgreSQL migration" --operator and --database my-db -# Only what matched the query, without items declared related at ingest +# Only what matched the query, without context declared related at ingest hydradb query "refund window" --no-follow-forceful-relations --database my-db ``` @@ -248,9 +248,9 @@ hydradb query "refund window" --no-follow-forceful-relations --database my-db | `--alpha` | Hybrid search weight (`0.0` keyword → `1.0` semantic) | | `--recency-bias` | Preference for newer content (`0.0` to `1.0`) | | `--graph-context` / `--no-graph-context` | Include context-graph paths (`graph`) in the answer | -| `--follow-forceful-relations` / `--no-follow-forceful-relations` | Also return items declared related at ingest (server default on) | +| `--follow-forceful-relations` / `--no-follow-forceful-relations` | Also return context declared related at ingest (server default on) | | `--llm` | Print the server-built `llm_prompt` verbatim on stdout, ready to inject into a model call. The request ID goes to stderr | -| `--acl` | A principal to answer as; repeatable. Results are limited to items whose access list admits one of them | +| `--acl` | A principal to answer as; repeatable. Results are limited to context whose access list admits one of them | | `--context` | Additional context to guide retrieval | | `--title` | Restrict the search to documents with this exact title, ignoring case. Repeatable | @@ -344,7 +344,7 @@ Use `--output json` to get machine-readable output that pipes into `jq`, Python, other tools: ```bash -# List items as JSON and pull out their IDs +# List context as JSON and pull out the IDs hydradb -o json list --database my-db | jq '.sources[].id' # Query and extract just the matched text @@ -365,7 +365,7 @@ done `query` returns the response body as the server sent it: `chunks` (each with `chunk_id`, `context_id`, `score` and `content`, plus `enrichment`, `enrichment_kind`, `received_at` and `temporal` when present), `graph`, `forceful_relations` and - `llm_prompt`. `list` returns the listed items under `sources`, each with its `id`. + `llm_prompt`. `list` returns the listed context under `sources`, each with its `id`. ## Source & Show Support diff --git a/plugins/mcp.mdx b/plugins/mcp.mdx index fff9a6f7..c45835cd 100644 --- a/plugins/mcp.mdx +++ b/plugins/mcp.mdx @@ -446,8 +446,8 @@ same scope names the rest of the product uses. See the | `hydradb_ingest` | Store a note or document (`text`) or a conversation (`turns`) as one context item; HydraDB enriches it and adds it to the context graph | | `hydradb_list` | List what is stored in a collection, one page at a time | | `hydradb_inspect` | Retrieve the original content of a stored item by ID | -| `hydradb_delete` | Remove stored items by ID | -| `hydradb_status` | Check whether ingested items have finished indexing | +| `hydradb_delete` | Remove stored context by ID | +| `hydradb_status` | Check whether ingested context has finished indexing | | `hydradb_list_collections` | List collections (sub-tenants) in a database | | `hydradb_delete_collection` | Permanently delete a collection and all of its data | | `hydradb_databases` | List the databases this connection can use, with the default marked. Available on OAuth connections | @@ -463,13 +463,13 @@ Sends the question to `POST /query` and returns the answer described under | `max_results` | number | No | Chunks to return, 1-50 (default: `10`) | | `mode` | string | No | `thinking` (default) expands the query, reranks and follows forceful relations; `fast` is one pass and quicker; `auto` lets HydraDB pick | | `graph_context` | boolean | No | Include related facts from the context graph (`graph[]`) in the answer (default: `true`) | -| `follow_forceful_relations` | boolean | No | Also return items declared related at ingest (see `forceful_relations` on `hydradb_ingest`), listed under Forceful relations with `[R1]` labels (default: `true`). They are followed in `thinking` mode | +| `follow_forceful_relations` | boolean | No | Also return context declared related at ingest (see `forceful_relations` on `hydradb_ingest`), listed under Forceful relations with `[R1]` labels (default: `true`). They are followed in `thinking` mode | | `operator` | string | No | `or`, `and`, or `phrase`. Switches the query to keyword retrieval, which matches the literal words instead of running hybrid semantic search. Leave unset for normal searches | | `source_ids` | string[] | No | Restrict the search to these item IDs (context IDs from earlier results or `hydradb_list`). No match returns an empty result | -| `titles` | string[] | No | Restrict the search to items whose **complete** title exactly matches any value, ignoring case | -| `recency_bias` | number | No | Favour recently updated items when ranking, 0-1 (default: `0`). Re-ranks only; it never excludes older items | +| `titles` | string[] | No | Restrict the search to context whose **complete** title exactly matches any value, ignoring case | +| `recency_bias` | number | No | Favour recently updated context when ranking, 0-1 (default: `0`). Re-ranks only; it never excludes older context | | `query_apps` | boolean | No | App-aware retrieval over connector content: exact IDs and actors, thread reconstruction, parent and child expansion (default: `false`) | -| `acl` | string[] | No | Principals to answer as: an email, a `domain:`, or a `group::`. Results are limited to items whose access list admits one of them. Omit to search everything the key can reach. See [Access control](/essentials/v2/access-control) | +| `acl` | string[] | No | Principals to answer as: an email, a `domain:`, or a `group::`. Results are limited to context whose access list admits one of them. Omit to search everything the key can reach. See [Access control](/essentials/v2/access-control) | | `collections` | string[] | No | Search several collections at once. Pass either this or `collection`, not both | | `database` | string | No | Database (tenant) scope override for this request | | `collection` | string | No | Collection (sub-tenant) scope override for this request | @@ -495,8 +495,8 @@ the same shape). See [Query](/essentials/v2/query) for every field. #### Filtering by document title Use `titles` when you know document names but not their IDs. The titles are -resolved to item IDs first, then the normal semantic or keyword query runs inside -those items. +resolved to context IDs first, then the normal semantic or keyword query runs inside +that context. ```json { @@ -553,8 +553,8 @@ connector content appear in one listing. | --------- | ---- | -------- | ----------- | | `ids` | array | No | Specific IDs to filter by | | `page` | number | No | Page number, 1-indexed (default: `1`) | -| `page_size` | number | No | Items per page, 1-100 | -| `acl` | string[] | No | Principals to answer as; the listing shows only items their access list admits | +| `page_size` | number | No | Rows per page, 1-100 | +| `acl` | string[] | No | Principals to answer as; the listing shows only context their access list admits | | `database` | string | No | Database (tenant) scope override for this request | | `collection` | string | No | Collection (sub-tenant) scope override for this request | @@ -631,7 +631,7 @@ HydraDB MCP also exposes tools for querying and writing [Cypher Graph Collection refuses with *"Source is still processing; retry deletion after ingestion completes"*, and the tool passes that back rather than reporting a deletion that did not happen. Retry once `hydradb_status` shows the item has finished. - This applies to freshly ingested items only; an item is listable and + This applies to freshly ingested context only; a context is listable and inspectable before it is deletable. From 7460be366466ec5bd2388e65cf4118f7820295bd Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Wed, 23 Sep 2026 23:49:45 +0530 Subject: [PATCH 08/17] docs: Delete Context points readers away from the deprecated data.success (PRO-1618) Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/v2/endpoint/delete-source.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-reference/v2/endpoint/delete-source.mdx b/api-reference/v2/endpoint/delete-source.mdx index cb14af3d..c9f8e491 100644 --- a/api-reference/v2/endpoint/delete-source.mdx +++ b/api-reference/v2/endpoint/delete-source.mdx @@ -195,7 +195,7 @@ The header always wins. Without it, the server default applies. ## Some additional notes - **Partial-success semantics:** Each ID is reported independently in `results[]`, and `deleted_count` totals the context actually removed. An ID that matched nothing comes back with `deleted: false` and an `error`, and does not stop the rest. One exception: if any ID in the request is still indexing, the whole request is refused and nothing is deleted. That is reported as `409` in strict mode, and as a `200` with `deleted_count: 0` by default. -- **`data.success` is deprecated:** it mirrors `deleted_count > 0`, so it is `false` for a delete that removed nothing even when the request itself returned `200`. Read `deleted_count` and `results[]` instead. +- **`data.success` is deprecated:** do not use it to decide anything. Whether the request succeeded is the HTTP status (or the envelope's top-level `success`); whether anything was removed is `deleted_count` and `results[]`. - **Retrieval drops deleted context immediately:** Even before background cleanup finishes, deleted IDs disappear from `/query` and `/context/list` responses.
From e96eff9f4b0a565afdcb48f3ea17729ff14da09b Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 00:00:59 +0530 Subject: [PATCH 09/17] docs: say context, not item, for ingested units Prose only: code, field names and cookbooks untouched. Connector resources, status results and schema fields are entries, not contexts. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 120 +++++++++--------- .../v2/endpoint/configure-connector.mdx | 4 +- .../v2/endpoint/connectors-overview.mdx | 2 +- .../v2/endpoint/create-connector.mdx | 2 +- .../endpoint/discover-connector-resources.mdx | 2 +- api-reference/v2/endpoint/fetch-content.mdx | 20 +-- api-reference/v2/endpoint/query-overview.mdx | 4 +- api-reference/v2/endpoint/source-status.mdx | 8 +- .../v2/endpoint/sources-overview.mdx | 8 +- api-reference/v2/endpoint/subgraph.mdx | 22 ++-- .../v2/endpoint/update-metadata-schema.mdx | 2 +- .../v2/endpoint/update-source-metadata.mdx | 2 +- api-reference/v2/error-responses.mdx | 18 +-- api-reference/v2/index.mdx | 8 +- api-reference/v2/sdks.mdx | 10 +- essentials/v2/access-control.mdx | 4 +- essentials/v2/api-results.mdx | 4 +- essentials/v2/architecture.mdx | 4 +- essentials/v2/attributes.mdx | 52 ++++---- essentials/v2/bring-your-own-graph.mdx | 34 ++--- essentials/v2/context-categories.mdx | 14 +- essentials/v2/context-graphs.mdx | 2 +- essentials/v2/databases-and-collections.mdx | 2 +- essentials/v2/graph-collections-byog.mdx | 2 +- essentials/v2/split-databases.mdx | 34 ++--- essentials/v2/webhooks.mdx | 14 +- get-started/v2/core-concepts.mdx | 8 +- plugins/claude-code.mdx | 12 +- plugins/cli.mdx | 34 ++--- plugins/mcp.mdx | 50 ++++---- plugins/openclaw.mdx | 18 +-- 31 files changed, 260 insertions(+), 260 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index 4188b578..d1ebd08d 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -68,11 +68,11 @@ Core raw HTTP responses (`/databases`, `/context/*`, `/query` and `/feedback`) a | Search | `POST /query` · `client.query()` | | Report back on query results | `POST /feedback` · `client.feedback.submit()` | | List context | `POST /context/list` · `client.context.list()` | -| Read an item's stored content | `GET /context/inspect` · `client.context.inspect()` | +| Read a context's stored content | `GET /context/inspect` · `client.context.inspect()` | | Delete context | `DELETE /context` · `client.context.delete()` | | Inspect graph relations | `GET /context/relations` · `client.context.relations()` | | Walk the context connected to one context | `GET /context/{id}/subgraph` · `client.context.subgraph()` | -| Edit an indexed item's attributes | `PATCH /context/{id}/metadata` · Python `client.context.update_source_metadata()` / TS `client.context.updateSourceMetadata()` | +| Edit an indexed context's attributes | `PATCH /context/{id}/metadata` · Python `client.context.update_source_metadata()` / TS `client.context.updateSourceMetadata()` | | Indexing webhooks | `/webhooks/indexing*` | ### Async lifecycle @@ -80,7 +80,7 @@ Core raw HTTP responses (`/databases`, `/context/*`, `/query` and `/feedback`) a Two operations are asynchronous: 1. **Database creation**: after `POST /databases`, poll `GET /databases/status` until `data.infra.ready_for_ingestion` is `true`. -2. **Ingestion**: after `POST /context/ingest`, poll `GET /context/status` until each item is searchable or fully complete. +2. **Ingestion**: after `POST /context/ingest`, poll `GET /context/status` until each context is searchable or fully complete. Searchable status: @@ -95,7 +95,7 @@ Failure status: - `database` (formerly `tenant_id`) is the hard isolation boundary. - `collection` (formerly `sub_tenant_id`) is a logical partition inside a database. `/query` also takes `collections` to search several at once. -- `context_id` is your id for one item. The ingest response reports it as `results[].id`; query chunks carry it as `context_id`. +- `context_id` is your id for one context. The ingest response reports it as `results[].id`; query chunks carry it as `context_id`. - `attributes` are declared, filterable fields; `custom_attributes` are free-form and not filterable. See [Attributes guide](#10-attributes-guide). ### Sending feedback @@ -104,7 +104,7 @@ Failure status: It is entirely optional, and there is no penalty for skipping it. Send it when you have something specific to report; a report on a query that disappointed you is worth far more than a stream of routine ones. If a query worked well and you want to say so, `rating: "positive"` is welcome too: it tells us what to preserve. -**Once you have finished the task, a short report on how the context actually performed is recommended.** The end of the task is when you know something you could not know at retrieval time: whether a chunk that looked plausible turned out to be stale, contradicted by another item, or never used at all. A single report then is worth more than one per query. +**Once you have finished the task, a short report on how the context actually performed is recommended.** The end of the task is when you know something you could not know at retrieval time: whether a chunk that looked plausible turned out to be stale, contradicted by another context, or never used at all. A single report then is worth more than one per query. Give it context. We see only a `request_id` and whatever prose you send, nothing about what you were doing, so a report that stands on its own is far more useful than one that assumes the session can be reconstructed. Worth a sentence each: @@ -160,7 +160,7 @@ Catch broadly. The point is that **nothing** escapes: narrowing to the SDK error **"Did not give me what I needed" is not the same as "errored".** A query that returned `200` with unhelpful results is exactly what this endpoint is for. A query that never returned (`4xx`/`5xx`, or the SDK raised) is not: handle the error and move on rather than reporting it. Feedback is a judgement about retrieval quality, and a query that produced no results has no retrieval to judge. Fix the request instead: a `404` means the database name is wrong, a `429` means back off, a `400` means the body was malformed. -**If you know the right answer, send it as `ground_truth`.** When you are running against a labelled set, or you know which item should have been returned, that is a far stronger signal than a comment: it can be scored without a human reading it. With `ground_truth` present, `feedback` prose is optional: +**If you know the right answer, send it as `ground_truth`.** When you are running against a labelled set, or you know which context should have been returned, that is a far stronger signal than a comment: it can be scored without a human reading it. With `ground_truth` present, `feedback` prose is optional: ```python try: @@ -217,7 +217,7 @@ With `enrich: true` (the default) HydraDB reads each context and extracts entiti ### Query -`POST /query` is the single retrieval endpoint. It searches every item in the collections you name. +`POST /query` is the single retrieval endpoint. It searches every context in the collections you name. - Scope with `collection` (one) or `collections` (a list, or `{ "name": weight }` to rank one collection above another). - `query_by: "hybrid"` (default) blends semantic and BM25 retrieval; `query_by: "text"` is BM25 keyword or phrase search. @@ -244,7 +244,7 @@ Each submission is its own record. Sending a second report about the same query ### Context graph -HydraDB builds a graph of entities and relations from every enriched item, and from any graph you supply with `graph_payload`. With `graph_context: true` (the default), a query returns `graph[]`: paths through that graph that connect the question to the results. Each path has: +HydraDB builds a graph of entities and relations from every enriched context, and from any graph you supply with `graph_payload`. With `graph_context: true` (the default), a query returns `graph[]`: paths through that graph that connect the question to the results. Each path has: - `origin`: `"query_path"` (grown from the entities in the query) or `"chunk_relation"` (the neighbourhood of a returned chunk). - `triplets`: the chain of `source`, `relation`, `target` hops. Every hop's `relation.chunk_id` names the chunk it was extracted from. @@ -614,7 +614,7 @@ Poll until `data.infra.ready_for_ingestion` is `true` (TypeScript: `data.infra.r `POST /context/ingest` · `client.context.ingest()` -One endpoint takes every item, text or conversation, into any collection of a database. The raw HTTP body is JSON and the list is called `context`. +One endpoint takes every context, text or conversation, into any collection of a database. The raw HTTP body is JSON and the list is called `context`. ```json { @@ -654,16 +654,16 @@ One endpoint takes every item, text or conversation, into any collection of a da | `database` | Required. The database to write to. | | `collection` | Optional. The collection to write to; the default collection when omitted. | | `context` | The list of contexts, at most 100. | -| `enrich` | Request-level default for every item's `enrich`. Default `true`. | -| `upsert` | Request-level default for every item's `upsert`. Default `true`. | -| `instructions` | Request-level default for every item's `instructions`. Default empty. | +| `enrich` | Request-level default for every context's `enrich`. Default `true`. | +| `upsert` | Request-level default for every context's `upsert`. Default `true`. | +| `instructions` | Request-level default for every context's `instructions`. Default empty. | | `graph_payload` | Optional. A graph you built yourself, keyed by `context_id`. See [Bring Your Own Graph](#bring-your-own-graph). | The request-level values apply to any context that does not set the field itself, so one call can enrich some contexts and store others as they are, or replace some and append others. ### Context fields -Each item carries exactly one of `text` or `conversation`. +Each context carries exactly one of `text` or `conversation`. | Field | Notes | |---|---| @@ -671,35 +671,35 @@ Each item carries exactly one of `text` or `conversation`. | `title` | Optional readable name, printed in `llm_prompt` and matchable with `titles` on `/query`. At most 1,024 bytes. | | `text` | Plain text or markdown. | | `conversation` | A list of `{ role, content }` turns. | -| `enrich` | Extract entities, relations and preferences from this item. Default: the request's `enrich`, else `true`. Set `false` to store the item only as searchable text. | -| `upsert` | Replace an existing item with the same `context_id`. Default: the request's `upsert`, else `true`. | -| `instructions` | Steer enrichment for this item. At most 4,000 characters. Default: the request's `instructions`. | -| `happened_at` | The date the item is about, `YYYY-MM-DD` only; a timestamp is a `400`. HydraDB records when it received the item separately and returns that as `received_at` on query chunks. | +| `enrich` | Extract entities, relations and preferences from this context. Default: the request's `enrich`, else `true`. Set `false` to store the context only as searchable text. | +| `upsert` | Replace an existing context with the same `context_id`. Default: the request's `upsert`, else `true`. | +| `instructions` | Steer enrichment for this context. At most 4,000 characters. Default: the request's `instructions`. | +| `happened_at` | The date the context is about, `YYYY-MM-DD` only; a timestamp is a `400`. HydraDB records when it received the context separately and returns that as `received_at` on query chunks. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. | | `custom_attributes` | Free-form fields. Not filterable. | -| `forceful_relations` | `{ "context_ids": [...], "properties": {} }`: the `context_id`s this item is linked to. `properties` is an optional flat map of string, number or boolean values (at most 1 KiB) stored on each edge; `id`, `created_at`, `relation_type`, `tenant_id` and `sub_tenant_id` are reserved keys. | -| `acl` | Principals allowed to retrieve the item: `user_email:a@x.com` (or a bare email), `group::`, `domain:acme.com`, `__public__`. Omit for unrestricted, `[]` for nobody. A malformed principal rejects the whole request with `400`. | -| `user_name` | The speaker for the item: the author of a text item, or the person in a conversation's `user` turns. Default `"User"`. | +| `forceful_relations` | `{ "context_ids": [...], "properties": {} }`: the `context_id`s this context is linked to. `properties` is an optional flat map of string, number or boolean values (at most 1 KiB) stored on each edge; `id`, `created_at`, `relation_type`, `tenant_id` and `sub_tenant_id` are reserved keys. | +| `acl` | Principals allowed to retrieve the context: `user_email:a@x.com` (or a bare email), `group::`, `domain:acme.com`, `__public__`. Omit for unrestricted, `[]` for nobody. A malformed principal rejects the whole request with `400`. | +| `user_name` | The speaker for the context: the author of a text context, or the person in a conversation's `user` turns. Default `"User"`. | -An unknown key is a `400` naming the key and listing the accepted ones, whether it is on the request, on an item, on a conversation turn or inside `forceful_relations`. +An unknown key is a `400` naming the key and listing the accepted ones, whether it is on the request, on a context, on a conversation turn or inside `forceful_relations`. ### Conversations - Roles are `user`, `assistant` and `system`. Any other role is a `400`; map roles such as `tool` or `human` before sending. -- `system` turns are never stored as facts. When neither the item nor the request sets `instructions`, they become the item's instructions, held to the same 4,000-character limit; otherwise they are dropped. A conversation of only `system` turns is a `400`. +- `system` turns are never stored as facts. When neither the context nor the request sets `instructions`, they become the context's instructions, held to the same 4,000-character limit; otherwise they are dropped. A conversation of only `system` turns is a `400`. - Consecutive turns with the same role are accepted and joined. -- The speaker is the item's `user_name`. A turn carries only `role` and `content`; any other key on a turn is a `400`. +- The speaker is the context's `user_name`. A turn carries only `role` and `content`; any other key on a turn is a `400`. - An empty list, or a turn with empty `content`, is a `400`. ### IDs and replacement - `upsert: true` (the default) **replaces**: re-ingesting a `context_id` deletes everything derived from the previous version (its chunks and its graph contribution) before writing the new one. It does not merge. -- `upsert` is per item, with the request value as the default. -- Give repeated text either a `context_id` or a distinct `title`, or the second item replaces the first. +- `upsert` is per context, with the request value as the default. +- Give repeated text either a `context_id` or a distinct `title`, or the second context replaces the first. ### Bring Your Own Graph -Skip extraction for an item and supply its entities and relations yourself with `graph_payload`, a map keyed by `context_id`: +Skip extraction for a context and supply its entities and relations yourself with `graph_payload`, a map keyed by `context_id`: ```json { @@ -721,15 +721,15 @@ Skip extraction for an item and supply its entities and relations yourself with } ``` -Every key in `graph_payload` must equal the `context_id` of an item in the same request; a key that matches nothing is a `400`. A keyed item is still chunked and embedded, so it stays searchable. Entity and relation shapes and caps are on [Bring Your Own Graph](/essentials/v2/bring-your-own-graph). +Every key in `graph_payload` must equal the `context_id` of a context in the same request; a key that matches nothing is a `400`. A keyed context is still chunked and embedded, so it stays searchable. Entity and relation shapes and caps are on [Bring Your Own Graph](/essentials/v2/bring-your-own-graph). ### Limits and validation - At most **100 contexts** per request, **1 MiB** of text per context, **8 MiB** of text per request. Split larger batches. - The whole body is capped at **16 MiB** (the JSON body, or the `context` form field on the multipart form). A larger one is a `413` with `request body too large`. -- `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each item. -- `attributes` are capped at **16 KiB** and `custom_attributes` at **1 KiB** per item, measured on the compact JSON encoding in UTF-8 bytes (keys and punctuation count). -- A validation error names the item it refers to as `context[N]`. +- `title` at most **1,024 bytes**; `instructions` at most **4,000 characters**, on the request and on each context. +- `attributes` are capped at **16 KiB** and `custom_attributes` at **1 KiB** per context, measured on the compact JSON encoding in UTF-8 bytes (keys and punctuation count). +- A validation error names the context it refers to as `context[N]`. - Ingest takes text only. To ingest a PDF, DOCX or CMS export, extract its text in your application and send it as `text`, one context per document. For tools such as Slack, Notion or Google Drive, use a [connector](/essentials/v2/connectors): synced content lands in the same database and is queried together with your own context. ### SDKs: the `context` form field @@ -758,8 +758,8 @@ The SDKs send a multipart form rather than a JSON body. The array goes in the `c } ``` -- `results[].id` is the item's `context_id`: the one you sent, or the generated one. Pass it to `GET /context/status`. -- `results[].infer` mirrors the item's `enrich`. +- `results[].id` is the context's `context_id`: the one you sent, or the generated one. Pass it to `GET /context/status`. +- `results[].infer` mirrors the context's `enrich`. - `results[].status` is `queued` or `failed`. A failed context carries `error` and `error_code`; the others in the request are still queued. - A `202` means queued, not searchable. Poll status before querying. @@ -775,7 +775,7 @@ Parameters: - `database`: required. - `ids`: one or more `context_id`s (repeat the parameter, or comma-separate). -- `collection`: **required if you ingested into one.** The lookup is scoped: omitting `collection`, or sending the wrong one, returns `indexing_status: "errored"` with `error_code: "FILE_NOT_FOUND"` and `message: "ID not found"` for an item that exists and is fully searchable. That is a scope miss, not an indexing failure, and it is indistinguishable from one unless you read `error_code`. +- `collection`: **required if you ingested into one.** The lookup is scoped: omitting `collection`, or sending the wrong one, returns `indexing_status: "errored"` with `error_code: "FILE_NOT_FOUND"` and `message: "ID not found"` for a context that exists and is fully searchable. That is a scope miss, not an indexing failure, and it is indistinguishable from one unless you read `error_code`. | Status | Searchable? | Meaning | |---|---:|---| @@ -803,7 +803,7 @@ Use `/webhooks/indexing` to receive terminal indexing events. Supported event: `indexing.status_changed`. -Payload shape (`id` is the item's `context_id`): +Payload shape (`id` is the context's `context_id`): ```json { @@ -893,7 +893,7 @@ Rules: | Recent operational updates | `recency_bias` above the default `0.4`, plus an `attributes` filter on status or doc type | | Connector content (Slack, Jira, Gmail) | `mode: "thinking"` (`query_apps` is on by default) | | Follow linked context | `mode: "thinking"` (`follow_forceful_relations` is on by default) | -| A known item or document | `ids: [...]` or `titles: [...]` | +| A known context or document | `ids: [...]` or `titles: [...]` | ### Examples @@ -1002,7 +1002,7 @@ Attribute-filtered search, on behalf of one user: | Field | Meaning | |---|---| | `chunk_id` | The chunk's id. Referenced from `graph[].triplets[].relation.chunk_id`. | -| `context_id` | The item this chunk came from. | +| `context_id` | The context this chunk came from. | | `score` | Relevance. Always present. | | `content` | The chunk's own text, verbatim. Enrichment is never concatenated into it. | | `enrichment` | A plain string: what enrichment extracted from this chunk (a preference, a fact). Omitted when there is none. | @@ -1010,7 +1010,7 @@ Attribute-filtered search, on behalf of one user: | `received_at` | When HydraDB received the context, as an RFC 3339 timestamp. This is the ingest time, not its `happened_at` (which is not returned). Omitted when no receipt time is recorded, as on context ingested before it existed; never sent empty. | | `temporal` | Present only when the query engaged temporal reasoning: `{ content, start_date, end_date }` entries, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`. | -Chunks carry almost nothing about their source: no title, url, collection or attributes, only `received_at`. `llm_prompt` prints the title, collection, last-updated date and url for the model. To read an item's stored content yourself, call `GET /context/inspect` with the chunk's `context_id`; to read its title and attributes, call `POST /context/list` with `ids: [context_id]`. +Chunks carry almost nothing about their source: no title, url, collection or attributes, only `received_at`. `llm_prompt` prints the title, collection, last-updated date and url for the model. To read a context's stored content yourself, call `GET /context/inspect` with the chunk's `context_id`; to read its title and attributes, call `POST /context/list` with `ids: [context_id]`. `graph[]`: paths through the context graph, query paths first, then paths expanded from the returned chunks. The list is deduplicated across both origins (a path found both ways is reported once, as a `query_path`) and is not capped. `[]` when `graph_context` is `false` or nothing connects. @@ -1026,7 +1026,7 @@ Chunks carry almost nothing about their source: no title, url, collection or att | `triplets[].relation.chunk_id` | The chunk the relation was extracted from. | | `path_summary` | One sentence summarizing the path. Never empty. | -`forceful_relations[]`: chunks pulled in because an item declared `forceful_relations` at ingest. Followed only in `thinking` mode; `[]` when none were declared, the query ran in `fast` mode, or `follow_forceful_relations` is `false`. +`forceful_relations[]`: chunks pulled in because a context declared `forceful_relations` at ingest. Followed only in `thinking` mode; `[]` when none were declared, the query ran in `fast` mode, or `follow_forceful_relations` is `false`. | Field | Meaning | |---|---| @@ -1052,13 +1052,13 @@ Sections, in order (a section with nothing in it is left out; when the query ret |---|---| | `# Query results` | The query, an `**Interpreted:**` line when an alias or resolved reference widened it, a `**Found:**` line counting what follows, a `**Note:**` line when a lookup degraded, and (when there is a result) the line telling the model to cite it by its number. | | `## Results` | One `### 1. title` block per chunk, in ranked order: relevance, collection, type, category (`enrichment_kind`, when set), id and last-updated date, the chunk's `content`, then `**Enrichment:**`. | -| `## Forceful relations` | One `### R1. title` block per forceful-relation chunk, with `**Linked from:**` naming the item that pulled it in. | +| `## Forceful relations` | One `### R1. title` block per forceful-relation chunk, with `**Linked from:**` naming the context that pulled it in. | | `## Related facts` | One line per graph path, such as `- [P1] **Refunds** -managed_by→ **Finance** (relevance 0.81) [1]`, with the `path_summary` indented under it unless it only restates the chain. A path without a reranked score has no parenthetical. | | `## Temporal facts` | A `**Duration:**` line first for a "how long between" question, then one line per dated fact the query engaged, with its resolved window, citing its result. | | `## Source facts` | App-native facts about the sources behind the results (who, role, where, thread, connector, synced). Prompt only. | | `## Profiles` | The entity profiles the query selected. Prompt only. | | `## Code search` | The repository code-search answer. Prompt only. | -| `## Sources` | Each item once: title, type, id, url and last-updated date. | +| `## Sources` | Each context once: title, type, id, url and last-updated date. | Citation labels: @@ -1165,7 +1165,7 @@ const messages = [ ]; ``` -Surface `llm_prompt` to the model as it is and let the model cite the labels. Map a cited `[1]` back to `chunks[0].context_id` (and `[R1]` to `forceful_relations[0].chunk.context_id`) when you need to link a citation to an item. +Surface `llm_prompt` to the model as it is and let the model cite the labels. Map a cited `[1]` back to `chunks[0].context_id` (and `[R1]` to `forceful_relations[0].chunk.context_id`) when you need to link a citation to a context. ### Structured output instead of a prompt @@ -1191,7 +1191,7 @@ When you render results yourself (a UI, a reranker, an eval), read `chunks[].con ## 10. Attributes guide -Two kinds of structured fields travel with an item: +Two kinds of structured fields travel with a context: | Field | Declared? | Filterable? | Use for | |---|---|---|---| @@ -1236,7 +1236,7 @@ Limits: up to 32 declared fields, and up to 6 embedding flags per database (`ena } ``` -To change an indexed item's attributes, re-ingest it with the same `context_id` (`upsert: true` replaces it), or merge new values in place with `PATCH /context/{id}/metadata`, whose body names the two maps `database_metadata` (declared attributes) and `additional_metadata` (custom attributes). +To change an indexed context's attributes, re-ingest it with the same `context_id` (`upsert: true` replaces it), or merge new values in place with `PATCH /context/{id}/metadata`, whose body names the two maps `database_metadata` (declared attributes) and `additional_metadata` (custom attributes). ### Filtering with attributes @@ -1281,7 +1281,7 @@ None of these calls takes anything beyond `database`, an optional `collection`, `POST /context/list` · `client.context.list()` -Lists every item in the collection, text and conversation alike, in one paginated listing. +Lists every context in the collection, text and conversation alike, in one paginated listing. ```ts const page = await client.context.list({ @@ -1302,12 +1302,12 @@ Parameters: - `ids`: only these `context_id`s (filters and paging still apply) - `page` (1-indexed, default `1`), `page_size` (`1` to `100`, default `50`) - `filters`: exact-match constraints, ANDed (`source_fields.title` matches a case-insensitive prefix). `source_fields` matches built-in fields such as `title`, `url`, `timestamp` and, for connector content, `app_provider`, `app_kind`, `app_external_id`, `app_parent_id`. The list filter keeps its own wire names for the two attribute maps: `filters.metadata` matches declared `attributes` and `filters.additional_metadata` matches `custom_attributes`. -- `include_fields`: projection, for example `["title", "timestamp"]`. `content` and `url` are not projectable (a `400`); read an item's content with `GET /context/inspect`. +- `include_fields`: projection, for example `["title", "timestamp"]`. `content` and `url` are not projectable (a `400`); read a context's content with `GET /context/inspect`. - `acl`: list as an identity; only context it may see is returned. Response `data`: `{ success, message, sources: [...], total, pagination }`. `sources` is the wire name for the listed context: one row per context with its `id`, title, timestamp and stored attributes, without content. `pagination` carries `page`, `page_size`, `total`, `total_pages`, `has_next` and `has_previous`. -### Fetch an item's stored content +### Fetch a context's stored content `GET /context/inspect` · `client.context.inspect()` @@ -1327,7 +1327,7 @@ item = client.context.inspect( | `url` | a time-limited `presigned_url` | | `both` (default) | content plus presigned URL | -`expiry_seconds` sets the URL lifetime (default `3600`). With `acl`, the item must be visible to that identity or the response is `404`. +`expiry_seconds` sets the URL lifetime (default `3600`). With `acl`, the context must be visible to that identity or the response is `404`. ### Inspect graph relations @@ -1348,7 +1348,7 @@ Returns `relations[]`, triplet groups with their evidence (predicate, the senten `GET /context/{id}/subgraph` · `client.context.subgraph()` (the SDKs call the query-string form, `GET /context/subgraph`, which also accepts an id containing `/`) -Returns every item reachable from one item through item-level links (declared forceful relations, a shared thread, parent and child), breadth-first up to `depth` hops, with the relations among them. An unknown id returns an empty subgraph, not an error. +Returns every context reachable from one context through context-level links (declared forceful relations, a shared thread, parent and child), breadth-first up to `depth` hops, with the relations among them. An unknown id returns an empty subgraph, not an error. ### Delete context @@ -1362,12 +1362,12 @@ const res = await client.context.delete({ }); ``` -One call deletes each item and everything derived from it (chunks and graph contribution), whatever its shape. +One call deletes each context and everything derived from it (chunks and graph contribution), whatever its shape. Response `data`: `results[]` (per id: `id`, `deleted`, `error`), `deleted_count` and `message`. Read `deleted_count` and each `results[].deleted`; the nested `data.success` is deprecated and only mirrors `deleted_count > 0`. - `deleted_count: 0` means the ids matched nothing in that scope. Check `collection`. -- An item that is still indexing refuses the whole request: nothing is deleted. By default the response is still `200`, with `deleted_count: 0`. Send the header `X-HydraDB-Delete-Status: strict` to get honest codes instead: `404` when nothing matched, `409` while an item is still indexing (retry after `Retry-After`), `500` when a store failed (retryable). +- A context that is still indexing refuses the whole request: nothing is deleted. By default the response is still `200`, with `deleted_count: 0`. Send the header `X-HydraDB-Delete-Status: strict` to get honest codes instead: `404` when nothing matched, `409` while a context is still indexing (retry after `Retry-After`), `500` when a store failed (retryable). --- @@ -1380,8 +1380,8 @@ Response `data`: `results[]` (per id: `id`, `deleted`, `error`), `deleted_count` | `400` | invalid parameters or malformed request | No | | `401` | missing or invalid API key | No | | `403` | authenticated but not permitted | No | -| `404` | database or context item not found | No | -| `409` | conflict: existing database, or (strict delete) an item still indexing | Usually no; retry a strict-delete `409` after indexing finishes | +| `404` | database or context not found | No | +| `409` | conflict: existing database, or (strict delete) a context still indexing | Usually no; retry a strict-delete `409` after indexing finishes | | `413` | request body too large | No | | `422` | semantic validation failure, including `TENANT_INFRA_NOT_READY` | Only for `TENANT_INFRA_NOT_READY`, after polling readiness | | `429` | rate limited | Yes with backoff | @@ -1400,10 +1400,10 @@ Response `data`: `results[]` (per id: `id`, `deleted`, `error`), `deleted_count` | `DATABASE_ALREADY_EXISTS` | duplicate database name | | `DATABASE_NOT_FOUND` | database missing or not visible to this key | | `TENANT_INFRA_NOT_READY` | `422`: the database exists but its infrastructure is still provisioning. This is what calling it before polling readiness returns | -| `FILE_NOT_FOUND` | id not found **in the selected scope**: usually a `collection` mismatch rather than a missing item | -| `SOURCE_PROCESSING` | the item is still indexing and cannot serve this request yet | +| `FILE_NOT_FOUND` | id not found **in the selected scope**: usually a `collection` mismatch rather than a missing context | +| `SOURCE_PROCESSING` | the context is still indexing and cannot serve this request yet | | `NOT_FOUND` | requested resource does not exist | -| `PROCESSING_FAILED` | indexing failed for an item | +| `PROCESSING_FAILED` | indexing failed for a context | | `RATE_LIMITED` | rate limit exceeded | | `INTERNAL_ERROR` | unexpected server error | | `BACKEND_ERROR` | `502`: an upstream dependency returned an error | @@ -1411,7 +1411,7 @@ Response `data`: `results[]` (per id: `id`, `deleted`, `error`), `deleted_count` ### Ingest validation (`400`) -The whole request is rejected, and the message names the item as `context[N]`, when an item has both `text` and `conversation` (or neither), a conversation breaks the rules in [Conversations](#conversations), `happened_at` is malformed, an `acl` principal is malformed, or a size limit in [Limits and validation](#limits-and-validation) is exceeded. A `graph_payload` key that matches no item is also a `400`, and the message names the key. +The whole request is rejected, and the message names the context as `context[N]`, when a context has both `text` and `conversation` (or neither), a conversation breaks the rules in [Conversations](#conversations), `happened_at` is malformed, an `acl` principal is malformed, or a size limit in [Limits and validation](#limits-and-validation) is exceeded. A `graph_payload` key that matches no context is also a `400`, and the message names the key. Retry only `429`, `500`, `502` and `503`; use bounded exponential backoff with jitter. @@ -1485,9 +1485,9 @@ Typical uses of that shape: a support agent that answers from policy while respe - [ ] Not handling `errored` (and `failed`) as terminal failures. - [ ] Omitting `collection` on `context.status` and reading the resulting `FILE_NOT_FOUND` as a genuine indexing failure. - [ ] Using `??` on `errorMessage`, which is `""` rather than null on some failures, so the fallback never fires. -- [ ] Sending both `text` and `conversation` on one item. +- [ ] Sending both `text` and `conversation` on one context. - [ ] Sending a full timestamp in `happened_at`. -- [ ] Reusing untitled text without a `context_id`, so the second item replaces the first. +- [ ] Reusing untitled text without a `context_id`, so the second context replaces the first. - [ ] Writing with one `collection` and reading with another. - [ ] Using attributes for user partitioning instead of `collection`. - [ ] Filtering on `custom_attributes`, or on an attribute the schema does not declare. @@ -1519,10 +1519,10 @@ Method names are the same in both SDKs except where noted; Python takes snake_ca | `client.query()` | `POST /query` | Search; returns `chunks`, `graph`, `forceful_relations`, `llm_prompt` | | `client.feedback.submit()` | `POST /feedback` | Report how a query performed | | `client.context.list()` | `POST /context/list` | List context | -| `client.context.inspect()` | `GET /context/inspect` | Read an item's stored content or a presigned URL | +| `client.context.inspect()` | `GET /context/inspect` | Read a context's stored content or a presigned URL | | `client.context.relations()` | `GET /context/relations` | Inspect graph relations | | `client.context.subgraph()` | `GET /context/subgraph` | Walk the context connected to one context | -| `client.context.update_source_metadata()` (TS `updateSourceMetadata()`) | `PATCH /context/{id}/metadata` | Merge new attribute values into an indexed item | +| `client.context.update_source_metadata()` (TS `updateSourceMetadata()`) | `PATCH /context/{id}/metadata` | Merge new attribute values into an indexed context | | `client.context.delete()` | `DELETE /context` | Delete context | Helpers: `verify_webhook_signature` (Python, `hydra_db.helpers`) and `verifyWebhookSignature` (TypeScript) verify webhook signatures. diff --git a/api-reference/v2/endpoint/configure-connector.mdx b/api-reference/v2/endpoint/configure-connector.mdx index 5aad939d..50b7b3aa 100644 --- a/api-reference/v2/endpoint/configure-connector.mdx +++ b/api-reference/v2/endpoint/configure-connector.mdx @@ -49,10 +49,10 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/configure' \ | Name | Description | | --- | --- | -| | Resources to activate. Each item corresponds to one entry from [Discover](/api-reference/v2/endpoint/discover-connector-resources). | +| | Resources to activate. Each entry corresponds to one entry from [Discover](/api-reference/v2/endpoint/discover-connector-resources). | | | How far back the first sync fetches historical data. Only applies to the initial sync; later syncs are incremental from the last cursor. Above `30`, some providers fetch the older history in background chunks, and the response then reports `backfill: true`. (default: `30`) | -### Resource item fields +### Resource entry fields | Name | Description | | --- | --- | diff --git a/api-reference/v2/endpoint/connectors-overview.mdx b/api-reference/v2/endpoint/connectors-overview.mdx index 433f4574..c1ae4203 100644 --- a/api-reference/v2/endpoint/connectors-overview.mdx +++ b/api-reference/v2/endpoint/connectors-overview.mdx @@ -48,7 +48,7 @@ API-Version: 2 - **Connector**: an authenticated connection to one external provider account. A single connector manages all resources synced from that account. - **Resource**: a syncable unit within a provider, such as a Slack channel, GitHub repo, Linear team or project, Notion database or page, or Gmail label. You activate resources individually via `/configure`. - **Cursor**: a per-resource bookmark of the last synced position. Sync is incremental: only content newer than the cursor is fetched on each run. -- **provider_account_scope**: an identifier for the external account (for example a Slack workspace ID or GitHub org). It is part of every synced item's ID, so two connectors for the same provider need distinct values. +- **provider_account_scope**: an identifier for the external account (for example a Slack workspace ID or GitHub org). It is part of every synced context's ID, so two connectors for the same provider need distinct values. ## Metadata on synced objects diff --git a/api-reference/v2/endpoint/create-connector.mdx b/api-reference/v2/endpoint/create-connector.mdx index e09edb01..6860370e 100644 --- a/api-reference/v2/endpoint/create-connector.mdx +++ b/api-reference/v2/endpoint/create-connector.mdx @@ -37,7 +37,7 @@ curl -X POST 'https://api.hydradb.com/connectors' \ | | Human-readable label for this connector. | | | Which database receives the synced data. (deprecated alias: `tenant_id`) | | | Default collection partition for synced objects. Individual resources can override this. (deprecated alias: `sub_tenant_id`; default: `""`) | -| | Identifier for the external account (e.g. Slack workspace ID, GitHub org name). It is part of every synced item's ID, so use a distinct value for each connector of the same provider. | +| | Identifier for the external account (e.g. Slack workspace ID, GitHub org name). It is part of every synced context's ID, so use a distinct value for each connector of the same provider. | | | Provider-specific credentials, matching the provider's `credential_schema` from [Get Connector Provider](/api-reference/v2/endpoint/get-connector-provider). Token-based providers typically take `{ "api_token": "..." }` or `{ "access_token": "..." }`. | | | Seconds between scheduled syncs. From `300` to `604800`; a few providers set a higher minimum or a lower maximum. (default: `3600`) | | | Instructions that steer how this connector's synced documents are ingested and indexed. Up to 4000 characters. | diff --git a/api-reference/v2/endpoint/discover-connector-resources.mdx b/api-reference/v2/endpoint/discover-connector-resources.mdx index 66f65668..3764876b 100644 --- a/api-reference/v2/endpoint/discover-connector-resources.mdx +++ b/api-reference/v2/endpoint/discover-connector-resources.mdx @@ -46,7 +46,7 @@ curl 'https://api.hydradb.com/connectors/{connector_id}/discover' \ -Each item in `resources` represents one syncable unit. Pass the `id` (as `resource_id`) and `resource_type` values to [Configure](/api-reference/v2/endpoint/configure-connector) to activate the ones you want. +Each entry in `resources` represents one syncable unit. Pass the `id` (as `resource_id`) and `resource_type` values to [Configure](/api-reference/v2/endpoint/configure-connector) to activate the ones you want. To page through a large workspace, pass `limit` and, on later calls, the `cursor` from the previous response. A paginated response adds `next_cursor` and `has_more`; without either parameter the full list is returned. diff --git a/api-reference/v2/endpoint/fetch-content.mdx b/api-reference/v2/endpoint/fetch-content.mdx index bf64aa9b..4d4f42f5 100644 --- a/api-reference/v2/endpoint/fetch-content.mdx +++ b/api-reference/v2/endpoint/fetch-content.mdx @@ -1,12 +1,12 @@ --- title: "Inspect Context" -description: "Inspect the stored content of a context item." +description: "Inspect the stored content of a context." openapi: "api-reference/v2/openapi.json GET /context/inspect" --- import { Field } from "/snippets/field.jsx"; -Specify the `id` of the context item you want to retrieve. The response carries the stored item, the enrichment the server wrote for it, and a download link, depending on `mode`. +Specify the `id` of the context you want to retrieve. The response carries the stored context, the enrichment the server wrote for it, and a download link, depending on `mode`. @@ -45,7 +45,7 @@ curl -G 'https://api.hydradb.com/context/inspect' \ | Name | Description | | --- | --- | -| | ID of the context item to fetch. | +| | ID of the context to fetch. | | | Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | | | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | | | What to return. See [Fetch modes](#fetch-modes). (default=`"both"`) | @@ -55,15 +55,15 @@ curl -G 'https://api.hydradb.com/context/inspect' \ | Mode | Returns | Use when | | --- | --- | --- | -| `content` | The stored item in `content` (or, when it is not UTF-8 text, base64-encoded in `content_base64`), plus `inferred_content`. `presigned_url` is `null`. | You want to render the content in-app or feed it to another model. | -| `url` | A presigned URL (`presigned_url`) for the stored item, valid for `expiry_seconds`. `content`, `content_base64` and `inferred_content` are `null`. | You want a client or a service to download the item directly without proxying through your backend. | +| `content` | The stored context in `content` (or, when it is not UTF-8 text, base64-encoded in `content_base64`), plus `inferred_content`. `presigned_url` is `null`. | You want to render the content in-app or feed it to another model. | +| `url` | A presigned URL (`presigned_url`) for the stored context, valid for `expiry_seconds`. `content`, `content_base64` and `inferred_content` are `null`. | You want a client or a service to download the context directly without proxying through your backend. | | `both` _(default)_ | Everything `content` returns **and** the presigned URL. | UI flows that show the text inline plus a download link. | -`inferred_content` is the enrichment the server wrote for the item, or `null` when there is none (for example an item ingested with `enrich: false`, or one whose enrichment has not finished). It is returned in `content` and `both` modes; `url` mode leaves it `null`. +`inferred_content` is the enrichment the server wrote for the context, or `null` when there is none (for example a context ingested with `enrich: false`, or one whose enrichment has not finished). It is returned in `content` and `both` modes; `url` mode leaves it `null`. ### Mode examples -These examples inspect an item ingested as text. +These examples inspect a context ingested as text. @@ -185,11 +185,11 @@ These examples inspect an item ingested as text. ## Behavior notes - **Text vs binary handling.** In `content` and `both` modes, `content` carries the stored item when it is valid UTF-8 text. When it is not, `content` is `null` and the bytes come back base64-encoded in `content_base64`. Check both fields when handling unknown content types. + **Text vs binary handling.** In `content` and `both` modes, `content` carries the stored context when it is valid UTF-8 text. When it is not, `content` is `null` and the bytes come back base64-encoded in `content_base64`. Check both fields when handling unknown content types. -- **Context ingested as text:** There is no separate original file. `content` is the text you sent (a `conversation` is stored as JSON, so its `content` is a JSON document), `content_type` reports how it was stored, and in `url` and `both` modes `presigned_url` downloads that same stored item. +- **Context ingested as text:** There is no separate original file. `content` is the text you sent (a `conversation` is stored as JSON, so its `content` is a JSON document), `content_type` reports how it was stored, and in `url` and `both` modes `presigned_url` downloads that same stored context. - **Recently ingested context:** Fetching immediately after ingestion may return a record before enrichment is ready. For reliable reads, use [Ingestion Status](/api-reference/v2/endpoint/source-status) first. -- **Presigned URL TTL:** The URL is valid only for `expiry_seconds`. Anyone with the URL can download the item during that window, so treat it as a short-lived secret. +- **Presigned URL TTL:** The URL is valid only for `expiry_seconds`. Anyone with the URL can download the context during that window, so treat it as a short-lived secret.
diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index 7c9067c8..aac260ea 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -57,7 +57,7 @@ For filter design, read [Attributes](/essentials/v2/attributes) before creating | Personalized answer | `collections={ "": 2, "": 1 }`, `query_by="hybrid"`, `mode="thinking"` | | A person's preferences only | `collection=""`, `query_by="hybrid"` | | Exact keyword or phrase | `query_by="text"`, `operator="phrase"` | -| Recent operational updates | `query_by="hybrid"`, `recency_bias=0.2-0.4`, `attributes` on the right kind of item | +| Recent operational updates | `query_by="hybrid"`, `recency_bias=0.2-0.4`, `attributes` on the right kind of context | | Mixed or unpredictable query complexity | `query_by="hybrid"`, `mode="auto"`; let HydraDB route each query to `fast` or `thinking` | ## Typical patterns @@ -158,7 +158,7 @@ Use text query when literal wording matters: legal clauses, SKUs, error codes, I | Key | Contents | | --- | --- | -| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content`, optional `enrichment` (a string), `enrichment_kind`, `received_at` (when HydraDB received the item) and `temporal`. No other source details; call `POST /context/list` with the `context_id` in `ids` for those. | +| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content`, optional `enrichment` (a string), `enrichment_kind`, `received_at` (when HydraDB received the context) and `temporal`. No other source details; call `POST /context/list` with the `context_id` in `ids` for those. | | `graph[]` | Paths through the context graph, deduplicated across both origins and not capped: `origin` (`query_path` or `chunk_relation`), `triplets[]` and a `path_summary`, which is never empty. Each hop's `relation.chunk_id` names the chunk it came from, and `relation.timestamp` (Unix epoch seconds) is present when the edge has one; a `chunk_relation` path is only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk. | | `forceful_relations[]` | Chunks linked with `forceful_relations` at ingest, each with the `via` that brought it in. Followed only in `thinking` mode. | | `llm_prompt` | A server-built markdown string, ready to inject into a model call: results cited `[1]`, forceful relations `[R1]`, related facts labelled `[P1]` in `graph[]` order with each path's relevance when it has one, then temporal facts and sources. | diff --git a/api-reference/v2/endpoint/source-status.mdx b/api-reference/v2/endpoint/source-status.mdx index 77da4354..e93dcf2c 100644 --- a/api-reference/v2/endpoint/source-status.mdx +++ b/api-reference/v2/endpoint/source-status.mdx @@ -8,7 +8,7 @@ import { Field } from "/snippets/field.jsx"; Since ingestion is asynchronous, use this endpoint to determine when context is ready to be retrieved. -Pass one or more IDs in `ids` to retrieve status. Works for every context item, whether you ingested it or a connector synced it. For more information, see the [Ingest](/essentials/v2/ingest) guide. +Pass one or more IDs in `ids` to retrieve status. Works for every context, whether you ingested it or a connector synced it. For more information, see the [Ingest](/essentials/v2/ingest) guide. **Prefer webhooks over polling?** Register a webhook for `indexing.status_changed` events and HydraDB will `POST` to your endpoint when content reaches a terminal state (`completed` or `errored`). See [Webhooks](/essentials/v2/webhooks) for setup and receiver examples. @@ -109,13 +109,13 @@ curl -G 'https://api.hydradb.com/context/status' \ -## Status item fields +## Status result fields Each entry in `data.statuses` describes one requested `id`: | Field | Type | Description | | --- | --- | --- | -| `id` | string | The context item ID you asked about (echoed back). | +| `id` | string | The context ID you asked about (echoed back). | | `indexing_status` | string | One of the [status values](#status-values) below. `errored` is terminal. | | `error_code` | string | Machine-readable reason an entry is `errored`; **empty string (`""`) when the entry is not errored.** See [`error_code` values](#error-code-values). | | `error_message` | string | Human-readable explanation of an ingestion-pipeline `error_code`. Empty otherwise, including for `FILE_NOT_FOUND`. | @@ -128,7 +128,7 @@ Each entry in `data.statuses` describes one requested `id`: | `error_code` | Meaning | What to do | | --- | --- | --- | -| `FILE_NOT_FOUND` | No item with this `id` exists in the given `database` and `collection`: usually a typo, an `id` that was never ingested, or an item that was deleted. | Fix the `id`, or ingest the item. Not a processing failure: retrying the status call will not change it. | +| `FILE_NOT_FOUND` | No context with this `id` exists in the given `database` and `collection`: usually a typo, an `id` that was never ingested, or a context that was deleted. | Fix the `id`, or ingest the context. Not a processing failure: retrying the status call will not change it. | | *ingestion-pipeline codes* | A genuine processing failure, reported as a numeric `E####` code (for example `E1001` parse failed, `E1002` unsupported format, `E4001` embedding failed). | Act on the specific code; see [Ingestion error codes](/api-reference/v2/error-responses#ingestion-error-codes). Many are re-ingest-and-retry; some are terminal (unsupported format, empty content). | diff --git a/api-reference/v2/endpoint/sources-overview.mdx b/api-reference/v2/endpoint/sources-overview.mdx index bd0adf39..d96f5349 100644 --- a/api-reference/v2/endpoint/sources-overview.mdx +++ b/api-reference/v2/endpoint/sources-overview.mdx @@ -10,13 +10,13 @@ description: "Quick reference for context management endpoints, their lifecycle, | Send text and conversations as context | `POST /context/ingest` with `context[]` | | Poll indexing progress | `GET /context/status` | | Browse stored context | `POST /context/list` | -| Read an item's stored content | `GET /context/inspect` | +| Read a context's stored content | `GET /context/inspect` | | Delete context | `DELETE /context` | | Inspect graph relations | `GET /context/relations` | -| Walk everything connected to one item | `GET /context/{id}/subgraph` | -| Update an item's metadata without re-ingesting | `PATCH /context/{id}/metadata` | +| Walk everything connected to one context | `GET /context/{id}/subgraph` | +| Update a context's metadata without re-ingesting | `PATCH /context/{id}/metadata` | -Ingest is text only: to add a file, extract its text and send it as an item. Content from connected apps arrives through [connectors](/essentials/v2/connectors) rather than this endpoint. +Ingest is text only: to add a file, extract its text and send it as a context. Content from connected apps arrives through [connectors](/essentials/v2/connectors) rather than this endpoint. ## Lifecycle diff --git a/api-reference/v2/endpoint/subgraph.mdx b/api-reference/v2/endpoint/subgraph.mdx index 3b8fe520..25c383e0 100644 --- a/api-reference/v2/endpoint/subgraph.mdx +++ b/api-reference/v2/endpoint/subgraph.mdx @@ -4,7 +4,7 @@ openapi: "api-reference/v2/openapi.json GET /context/subgraph" description: "Everything connected to one context: its thread, its replies, its parents and children, and the context it links to." --- -This endpoint returns the **connected subgraph** of one ingested item: every item reachable from it through item-level relations, traversed breadth-first up to `depth` hops, together with the relations among those members and the structural graph around them (entities, comments, attachments, people). +This endpoint returns the **connected subgraph** of one ingested context: every context reachable from it through context-level relations, traversed breadth-first up to `depth` hops, together with the relations among those members and the structural graph around them (entities, comments, attachments, people). It answers a different question from [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Relations are the entity-and-predicate triplets *extracted from text* (`PaymentsWorker → depends_on → OrdersDB`). The subgraph is about *whole contexts*: which Slack message replies to which, which page links to which, which ticket a comment belongs to. Use it after [Query](/api-reference/v2/endpoint/query) or [List Context](/api-reference/v2/endpoint/list-documents) when a single result is not enough and you need what surrounds it. @@ -37,17 +37,17 @@ hydradb --output json subgraph slack_C0BE77_1788320073 | jq '.sources[].source_i ## Parameters -- **`id`** is the item to start from: any `id` returned by Query, List Context or Ingest. This query-string form takes any id, including one that contains `/`. `GET /context/{id}/subgraph` is the same read with the id as a URL-encoded path segment; it cannot carry an id containing a literal `/`. +- **`id`** is the context to start from: any `id` returned by Query, List Context or Ingest. This query-string form takes any id, including one that contains `/`. `GET /context/{id}/subgraph` is the same read with the id as a URL-encoded path segment; it cannot carry an id containing a literal `/`. - **`depth`** ranges from `1` to `10` hops (default `5`). **`max_sources`** ranges from `1` to `1000` members (default `200`); when it clips the traversal, `is_truncated` is `true`. ## How contexts connect -Every member except the start item records how the traversal found it: +Every member except the start context records how the traversal found it: -- **`discovered_relation`** names the mechanism. It is `same_thread` when the member shares a thread with an item already in the subgraph (Slack replies, ticket comments); `parent` or `child` for a hierarchy tie (a comment and the message it is under, a page and its section); or the relation type of an explicit `relates_to` link declared at ingest (`reply_to`, `references`, whatever the ingest named it). +- **`discovered_relation`** names the mechanism. It is `same_thread` when the member shares a thread with a context already in the subgraph (Slack replies, ticket comments); `parent` or `child` for a hierarchy tie (a comment and the message it is under, a page and its section); or the relation type of an explicit `relates_to` link declared at ingest (`reply_to`, `references`, whatever the ingest named it). - **`discovered_via`** is the `source_id` of the already-admitted member this one was first reached *from*. Follow it back and you rebuild the traversal tree: which reply hangs off which message, which page led to which. -Traversal is breadth-first, so `depth` on each member is its distance from the start item. The start item itself is a member at depth `0`, with neither field set. +Traversal is breadth-first, so `depth` on each member is its distance from the start context. The start context itself is a member at depth `0`, with neither field set. @@ -155,19 +155,19 @@ Traversal is breadth-first, so `depth` on each member is its distance from the s ## Reading the response -- **`sources[]`** are the members, the start item included at `depth: 0`. Every `source_id` is an id you can pass to [Inspect Context](/api-reference/v2/endpoint/fetch-content) for the full content, or back to this endpoint to re-centre the subgraph on it. `discovered_via` on each member is another member's `source_id`, so the list is also a tree. `hydration` is `resolved` (ingested), `stub` (a relation points at it but it is not ingested yet) or `placeholder`. Fields a member does not have are omitted. -- **`relations[]`** are the item-level relations *among the members* (declared `relates_to` links, plus `same_thread` and `child_of`), in the same triplet shape as [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Their endpoints are `SOURCE` entities whose `entity_id` is the item's id. -- **`auxiliary_relations[]`** is the structural graph around the members: which person sent a message, which entities are mentioned in it, which comments and attachments hang off it. These are recorded from the item itself, not extracted from text, so their `context` is empty. +- **`sources[]`** are the members, the start context included at `depth: 0`. Every `source_id` is an id you can pass to [Inspect Context](/api-reference/v2/endpoint/fetch-content) for the full content, or back to this endpoint to re-centre the subgraph on it. `discovered_via` on each member is another member's `source_id`, so the list is also a tree. `hydration` is `resolved` (ingested), `stub` (a relation points at it but it is not ingested yet) or `placeholder`. Fields a member does not have are omitted. +- **`relations[]`** are the context-level relations *among the members* (declared `relates_to` links, plus `same_thread` and `child_of`), in the same triplet shape as [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). Their endpoints are `SOURCE` entities whose `entity_id` is the context's id. +- **`auxiliary_relations[]`** is the structural graph around the members: which person sent a message, which entities are mentioned in it, which comments and attachments hang off it. These are recorded from the context itself, not extracted from text, so their `context` is empty. - **Not included:** the chunk-level entity relations that [Query](/api-reference/v2/endpoint/query) returns as graph paths in `graph[]`. Those are a different read. ## Some additional notes - **An unknown `id` is an empty subgraph, not an error.** The endpoint does not confirm or deny that an item exists; the same answer comes back for an id that was never ingested and for one the `acl` principals may not see. + **An unknown `id` is an empty subgraph, not an error.** The endpoint does not confirm or deny that a context exists; the same answer comes back for an id that was never ingested and for one the `acl` principals may not see. -- **An item nothing links to** comes back as a one-member subgraph: itself, at depth `0`, with `max_depth_reached: 0`. That is a real answer ("this stands alone"), distinct from an unknown id, which has no members. -- **Bounding the traversal.** Threads and hierarchies can be large. `depth` bounds how far the walk goes; `max_sources` bounds how many members it returns. When `max_sources` clips it, `is_truncated` is `true` and the members you have are the ones closest to the start item. `auxiliary_truncated` reports the same for the structural graph. +- **A context nothing links to** comes back as a one-member subgraph: itself, at depth `0`, with `max_depth_reached: 0`. That is a real answer ("this stands alone"), distinct from an unknown id, which has no members. +- **Bounding the traversal.** Threads and hierarchies can be large. `depth` bounds how far the walk goes; `max_sources` bounds how many members it returns. When `max_sources` clips it, `is_truncated` is `true` and the members you have are the ones closest to the start context. `auxiliary_truncated` reports the same for the structural graph. - **Completeness.** A context's links populate once its `indexing_status` reaches `completed`. Context still in `graph_creation` may appear with fewer connections than they will have. - **Cost.** One request fans out into a bounded series of graph reads, so it is rate-limited like a Query, not like a status poll. diff --git a/api-reference/v2/endpoint/update-metadata-schema.mdx b/api-reference/v2/endpoint/update-metadata-schema.mdx index 4ff89dfc..382815c4 100644 --- a/api-reference/v2/endpoint/update-metadata-schema.mdx +++ b/api-reference/v2/endpoint/update-metadata-schema.mdx @@ -86,7 +86,7 @@ const response = await fetch("https://api.hydradb.com/databases/acme_corp/metada | --- | --- | | | New metadata schema fields to append. Must contain at least one field. | -Each `add_fields[]` item uses the same field shape as `database_metadata_schema` on [Create Database](/api-reference/v2/endpoint/create-tenant): +Each `add_fields[]` entry uses the same field shape as `database_metadata_schema` on [Create Database](/api-reference/v2/endpoint/create-tenant): | Field | Description | | --- | --- | diff --git a/api-reference/v2/endpoint/update-source-metadata.mdx b/api-reference/v2/endpoint/update-source-metadata.mdx index 3bc9bcb6..bc415b26 100644 --- a/api-reference/v2/endpoint/update-source-metadata.mdx +++ b/api-reference/v2/endpoint/update-source-metadata.mdx @@ -9,7 +9,7 @@ import { Field } from "/snippets/field.jsx"; Use this endpoint when you know a source ID and need to update its metadata or access-control list in place. It updates both the source row and the indexed chunk metadata used by query and list filters. - This endpoint uses older names for the fields you set at ingest: an item's `attributes` are `database_metadata` here, and its `custom_attributes` are `additional_metadata`. The source ID is the item's `context_id`. + This endpoint uses older names for the fields you set at ingest: a context's `attributes` are `database_metadata` here, and its `custom_attributes` are `additional_metadata`. The source ID is the context's `context_id`. ```http diff --git a/api-reference/v2/error-responses.mdx b/api-reference/v2/error-responses.mdx index c21605bf..d9082167 100644 --- a/api-reference/v2/error-responses.mdx +++ b/api-reference/v2/error-responses.mdx @@ -44,8 +44,8 @@ Use `error.code` for branching and log `meta.request_id` for every failed reques | `400` | Invalid parameters or malformed request | No | | `401` | Missing, expired, or invalid API key | No | | `403` | Authenticated, but not permitted for the resource | No | -| `404` | Database, context item, or related resource was not found | No | -| `409` | Conflict, usually an existing database, or a strict-mode delete of an item that is still indexing | Usually no | +| `404` | Database, context, or related resource was not found | No | +| `409` | Conflict, usually an existing database, or a strict-mode delete of a context that is still indexing | Usually no | | `413` | Request body too large, for example a `POST /context/ingest` body over 16 MiB. The error code is `INVALID_INPUT` | No; send a smaller request | | `422` | Well-formed request that failed validation | No | | `429` | Rate limit exceeded | Yes, with backoff | @@ -62,7 +62,7 @@ Use `error.code` for branching and log `meta.request_id` for every failed reques | `DATABASE_ALREADY_EXISTS` | `409` | `POST /databases` received a `database` (formerly `tenant_id`) that is already in use. | | `DATABASE_NOT_FOUND` | `404` | The requested database does not exist or is not visible to the current API key. | | `NOT_FOUND` | `404` | The requested context id does not exist in the selected database/collection. | -| `SOURCE_PROCESSING` | `409` | A strict-mode delete (`X-HydraDB-Delete-Status: strict`) named an item that is still indexing. Retry after ingestion completes; see the `Retry-After` header. | +| `SOURCE_PROCESSING` | `409` | A strict-mode delete (`X-HydraDB-Delete-Status: strict`) named a context that is still indexing. Retry after ingestion completes; see the `Retry-After` header. | | `VALIDATION_ERROR` | `422` | The request shape was valid JSON/form data, but one or more fields failed semantic validation. | | `TENANT_INFRA_NOT_READY` | `422` | The database exists but its infrastructure is still provisioning. Poll [`GET /databases/status`](/api-reference/v2/endpoint/tenant-status) until `data.infra.ready_for_ingestion` is `true`. | | `RATE_LIMITED` | `429` | The API key exceeded its current rate limit. | @@ -79,16 +79,16 @@ Endpoint pages list the most common codes for that operation. New codes may be a ## Ingestion error codes -Asynchronous ingestion failures surface a numeric `E####` code in the `error_code` field of [`GET /context/status`](/api-reference/v2/endpoint/source-status) responses and `indexing.status_changed` [webhook](/essentials/v2/webhooks) payloads. Unlike the HTTP `error.code` values above (which describe why a *request* was rejected), these describe why a specific *item* failed to index. +Asynchronous ingestion failures surface a numeric `E####` code in the `error_code` field of [`GET /context/status`](/api-reference/v2/endpoint/source-status) responses and `indexing.status_changed` [webhook](/essentials/v2/webhooks) payloads. Unlike the HTTP `error.code` values above (which describe why a *request* was rejected), these describe why a specific *context* failed to index. -Many storage- and capacity-related ingestion errors are **transient**: the pipeline retries them automatically with backoff, and they typically self-resolve within minutes. A code appearing in `error_code` does not by itself mean the item has failed permanently; only treat an item as a real failure once it reaches the terminal `errored` status. +Many storage- and capacity-related ingestion errors are **transient**: the pipeline retries them automatically with backoff, and they typically self-resolve within minutes. A code appearing in `error_code` does not by itself mean the context has failed permanently; only treat a context as a real failure once it reaches the terminal `errored` status. | Code | Meaning | Severity | |---|---|---| | `E6001` | Vector-store storage/indexing error while persisting processed data. The pipeline retries automatically and it usually clears within minutes. User message: *"Failed to store the processed data. Please try again. If the issue persists, contact support@hydradb.com."* | **Transient** (retryable) | -`E6001` is **transient**, not terminal. If you observe it on an in-flight item, keep polling [`/context/status`](/api-reference/v2/endpoint/source-status): the item normally advances to `graph_creation` / `completed` on a subsequent retry with no action on your part. Only contact support if the item is still reported as `errored` after retries are exhausted. +`E6001` is **transient**, not terminal. If you observe it on an in-flight context, keep polling [`/context/status`](/api-reference/v2/endpoint/source-status): the context normally advances to `graph_creation` / `completed` on a subsequent retry with no action on your part. Only contact support if the context is still reported as `errored` after retries are exhausted. ## Retry pattern @@ -250,10 +250,10 @@ Database creation is asynchronous. After `POST /databases`, poll [`GET /database ### Ingestion validation errors -`POST /context/ingest` validates every item before queuing any of them, and the error message names the failing item as `context[N]`. Common causes: +`POST /context/ingest` validates every context before queuing any of them, and the error message names the failing context as `context[N]`. Common causes: -- The body, an item, a conversation turn or `forceful_relations` carries a key the API does not accept. The error names the key and lists the accepted fields, for example `invalid request body: unknown field "is_markdown"; accepted request fields are ...`. -- An item has neither `text` nor `conversation`, or has both. +- The body, a context, a conversation turn or `forceful_relations` carries a key the API does not accept. The error names the key and lists the accepted fields, for example `invalid request body: unknown field "is_markdown"; accepted request fields are ...`. +- A context has neither `text` nor `conversation`, or has both. - A conversation turn has a role other than `user`, `assistant` or `system`, or empty `content`; or the conversation has only `system` turns. - `happened_at` is not a `YYYY-MM-DD` date. - A `context_id`, or an id in `forceful_relations.context_ids`, contains a comma, is longer than 100 bytes, or starts with `att_` or `cmt_`. diff --git a/api-reference/v2/index.mdx b/api-reference/v2/index.mdx index ade94cf2..fb7d9b70 100644 --- a/api-reference/v2/index.mdx +++ b/api-reference/v2/index.mdx @@ -28,7 +28,7 @@ description: "Single reference to all HydraDB endpoints" | `collection` | Optional partition inside a database, often a user, team, account, or customer. | Use it when one database contains data for multiple users or customers. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). Read more about our [multi-tenant architecture](/essentials/v2/databases-and-collections) | | [Context](/essentials/v2/ingest) | A `text` or a `conversation`, sent in the `context` list of `POST /context/ingest`. | Everything you ingest. Shared context goes in a shared collection; a person's preferences go in their own. | | `database_metadata_schema` | Database-level fields you define up front so metadata can be filtered or queried consistently. | Use it for stable fields like department, customer, region, plan, category, or compliance label. | -| `attributes` | Declared, filterable fields on an item, matching `database_metadata_schema`; `custom_attributes` are free-form. | Send them at ingest; filter with `attributes` on `/query`. | +| `attributes` | Declared, filterable fields on a context, matching `database_metadata_schema`; `custom_attributes` are free-form. | Send them at ingest; filter with `attributes` on `/query`. | | `ids` | IDs returned by ingestion or visible from `/context/list`. | Use them when polling processing status, inspecting content, listing a specific subset, deleting context, or inspecting relations. | ## End-to-end lifecycle @@ -102,11 +102,11 @@ SDK methods mirror the API: `client..()` maps to the correspondin | [`/databases/stats`](/api-reference/v2/endpoint/tenant-stats) | `GET` | `databases.stats` | Get usage statistics | You want to monitor object counts for a database. | | [`/context/ingest`](/api-reference/v2/endpoint/ingest-context) | `POST` | `context.ingest` | Ingest context | You are sending text or conversations. | | [`/context/status`](/api-reference/v2/endpoint/source-status) | `GET` | `context.status` | Check processing status | You have IDs from ingestion and need to know when they are queryable. | -| [`/context/inspect`](/api-reference/v2/endpoint/fetch-content) | `GET` | `context.inspect` | Read an item's stored content | You need the full stored content behind a `context_id`, such as the item a query chunk came from. For its title and attributes, use `POST /context/list` with `ids`. | +| [`/context/inspect`](/api-reference/v2/endpoint/fetch-content) | `GET` | `context.inspect` | Read a context's stored content | You need the full stored content behind a `context_id`, such as the context a query chunk came from. For its title and attributes, use `POST /context/list` with `ids`. | | [`/context/list`](/api-reference/v2/endpoint/list-documents) | `POST` | `context.list` | Browse context | You need pagination, filters, field projection, or a specific subset by `ids`. | -| [`/context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) | `PATCH` | `context.update_source_metadata` | Update an item's metadata | You need to change one existing item's attributes (`database_metadata`) or custom attributes (`additional_metadata`) without re-ingesting. | +| [`/context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) | `PATCH` | `context.update_source_metadata` | Update a context's metadata | You need to change one existing context's attributes (`database_metadata`) or custom attributes (`additional_metadata`) without re-ingesting. | | [`/context`](/api-reference/v2/endpoint/delete-source) | `DELETE` | `context.delete` | Delete context | You need to remove context by ID. | -| [`/context/relations`](/api-reference/v2/endpoint/source-relations) | `GET` | `context.relations` | Inspect entity relationships | You need graph relations for an item or collection. | +| [`/context/relations`](/api-reference/v2/endpoint/source-relations) | `GET` | `context.relations` | Inspect entity relationships | You need graph relations for a context or collection. | | [`/context/{id}/subgraph`](/api-reference/v2/endpoint/subgraph) | `GET` | `context.subgraph` | Walk everything connected to one context | You need a context's thread, replies, parents, children and linked context. | | [`/query`](/api-reference/v2/endpoint/query) | `POST` | `query` | Retrieve context | You need ranked chunks, graph paths, declared relations and a prompt-ready `llm_prompt`, with `hybrid` or `text` matching across one or more collections. | diff --git a/api-reference/v2/sdks.mdx b/api-reference/v2/sdks.mdx index bce55616..175fddaa 100644 --- a/api-reference/v2/sdks.mdx +++ b/api-reference/v2/sdks.mdx @@ -179,7 +179,7 @@ while (true) { ### Ingest context -Everything you ingest is a context item: one `text` or one `conversation`, with optional fields such as `context_id`, `title`, `happened_at` and `attributes`. The SDK sends a multipart form and puts the item list, as a JSON string, in the `context` field. +Everything you ingest is a context: one `text` or one `conversation`, with optional fields such as `context_id`, `title`, `happened_at` and `attributes`. The SDK sends a multipart form and puts the context list, as a JSON string, in the `context` field. ```python Python SDK @@ -387,7 +387,7 @@ const filtered = await client.query({ ``` -Chunks carry no title or URL; `llm_prompt` prints them for the model. To show an item's title and attributes yourself, list it by its `context_id` with `context.list` and `ids` (below); `context.inspect` returns its stored content. For every request and response field, see [Query](/essentials/v2/query) and the [Query reference](/api-reference/v2/endpoint/query); for injecting `llm_prompt` and mapping citations back, see [How to Use API Results](/essentials/v2/api-results). +Chunks carry no title or URL; `llm_prompt` prints them for the model. To show a context's title and attributes yourself, list it by its `context_id` with `context.list` and `ids` (below); `context.inspect` returns its stored content. For every request and response field, see [Query](/essentials/v2/query) and the [Query reference](/api-reference/v2/endpoint/query); for injecting `llm_prompt` and mapping citations back, see [How to Use API Results](/essentials/v2/api-results). ### Browse, inspect, and delete @@ -415,7 +415,7 @@ item = client.context.inspect( id="refund-policy", ) -# Graph relations extracted from one item. +# Graph relations extracted from one context. relations = client.context.relations( database="my_first_database", collection="support", @@ -452,7 +452,7 @@ const item = await client.context.inspect({ id: "refund-policy", }); -// Graph relations extracted from one item. +// Graph relations extracted from one context. const relations = await client.context.relations({ database: "my_first_database", collection: "support", @@ -468,7 +468,7 @@ const deleted = await client.context.delete({ ``` -A delete reports each id in `results[]` with a `deleted_count`. An item that is still indexing cannot be deleted yet; see [Delete Context](/api-reference/v2/endpoint/delete-source). +A delete reports each id in `results[]` with a `deleted_count`. A context that is still indexing cannot be deleted yet; see [Delete Context](/api-reference/v2/endpoint/delete-source). ## Response envelope diff --git a/essentials/v2/access-control.mdx b/essentials/v2/access-control.mdx index a3e66a12..1fdb0f84 100644 --- a/essentials/v2/access-control.mdx +++ b/essentials/v2/access-control.mdx @@ -48,7 +48,7 @@ Limits: 1000 principals per document, 256 characters per principal. Past that, u ## 3. Set an ACL -### At ingest, on any item +### At ingest, on any context Each entry in `context` accepts an `acl` list, whether it is a `text` or a `conversation`: @@ -60,7 +60,7 @@ Each entry in `context` accepts an `acl` list, whether it is a `text` or a `conv } ``` -Omit `acl` and the item is unrestricted. A malformed principal rejects the whole request with `400` rather than ingesting the document unprotected. +Omit `acl` and the context is unrestricted. A malformed principal rejects the whole request with `400` rather than ingesting the document unprotected. ### On an existing source, without re-ingesting diff --git a/essentials/v2/api-results.mdx b/essentials/v2/api-results.mdx index debc6c9f..394eb963 100644 --- a/essentials/v2/api-results.mdx +++ b/essentials/v2/api-results.mdx @@ -201,7 +201,7 @@ for (const rel of result.data.forcefulRelations) { ## 4. Showing source details -A chunk carries only `chunk_id`, `context_id`, `score`, `content`, `enrichment`, `enrichment_kind`, `received_at` (when HydraDB received the context) and `temporal`. It has no title, url, collection or attributes. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show an item's title, timestamp or attributes in your own UI, list it by its `context_id` with [`POST /context/list`](/api-reference/v2/endpoint/list-documents): +A chunk carries only `chunk_id`, `context_id`, `score`, `content`, `enrichment`, `enrichment_kind`, `received_at` (when HydraDB received the context) and `temporal`. It has no title, url, collection or attributes. `llm_prompt` prints the title, url, collection and last-updated date for the model. To show a context's title, timestamp or attributes in your own UI, list it by its `context_id` with [`POST /context/list`](/api-reference/v2/endpoint/list-documents): ```bash curl -X POST 'https://api.hydradb.com/context/list' \ @@ -211,7 +211,7 @@ curl -X POST 'https://api.hydradb.com/context/list' \ -d '{ "database": "acme", "collection": "company", "ids": ["refund-policy"] }' ``` -The row carries `title`, `timestamp` and the context's attributes (under `metadata` and `additional_metadata`, the list response's names for `attributes` and `custom_attributes`). [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) with the same `context_id` returns the item's stored content. +The row carries `title`, `timestamp` and the context's attributes (under `metadata` and `additional_metadata`, the list response's names for `attributes` and `custom_attributes`). [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) with the same `context_id` returns the context's stored content. Fetch it lazily, when a citation is opened, rather than for every chunk on every query. diff --git a/essentials/v2/architecture.mdx b/essentials/v2/architecture.mdx index 4dd42341..cc2cc885 100644 --- a/essentials/v2/architecture.mdx +++ b/essentials/v2/architecture.mdx @@ -112,7 +112,7 @@ flowchart LR style Errored fill:#ef4444,stroke:#b91c1c,stroke-width:2px,color:#f8fafc,stroke-linecap:round ``` -Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) with the returned `id` to follow each item through the pipeline. Two practical notes: +Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) with the returned `id` to follow each context through the pipeline. Two practical notes: - **`graph_creation` is already queryable.** Chunks become retrievable as soon as embedding finishes; you only need to wait for `completed` when you specifically need full graph context (`graph_context: true` on query). - **Failures surface with detail.** An `errored` status comes back with an `error_code` and `error_message` so you can distinguish parse failures from validation problems from infrastructure issues. @@ -175,7 +175,7 @@ A short cheat sheet for the parameters you'll touch most often, and where each o | `database` | Every call | Selects the isolated workspace. Required on every request. | | `collection` | Ingest + query | Narrows data to a user, team, or workspace inside the database. Use a single collection for writes and single-scope queries. | | `collections` | Query | Preferred query-time collection selector. Use a one-element list, a multi-scope list with equal weights, or a weighted object for fanout ranking. | -| `attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Declared, filterable fields on an item. Must match the [database metadata schema](/essentials/v2/attributes) declared at database creation. | +| `attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Declared, filterable fields on a context. Must match the [database metadata schema](/essentials/v2/attributes) declared at database creation. | | `custom_attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Free-form fields per context. Stored with the context; not filterable with `attributes`. | | `attributes` | [Query](/api-reference/v2/endpoint/query) | Deterministic narrowing with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on declared attributes. | | `alpha` | [Query](/api-reference/v2/endpoint/query) | Blends semantic vs. keyword (BM25) scores in `query_by: "hybrid"`. | diff --git a/essentials/v2/attributes.mdx b/essentials/v2/attributes.mdx index 95150829..ba226b24 100644 --- a/essentials/v2/attributes.mdx +++ b/essentials/v2/attributes.mdx @@ -3,11 +3,11 @@ title: "Attributes" description: "Declare filterable attributes in the database schema, attach attributes and custom attributes to context at ingest, and filter queries with the attributes operator language." --- -Attributes are structured values you attach to each item you ingest. Use them when you already know a hard constraint before retrieval runs, such as `department=legal`, `region=us`, `status=published` or `priority >= 5`. +Attributes are structured values you attach to each context you ingest. Use them when you already know a hard constraint before retrieval runs, such as `department=legal`, `region=us`, `status=published` or `priority >= 5`. -An item carries two kinds: +A context carries two kinds: -| Kind | Sent on an item as | Declared in the schema | Filterable at query time | Stored cap per item | +| Kind | Sent on a context as | Declared in the schema | Filterable at query time | Stored cap per context | | --- | --- | --- | --- | --- | | Attributes | `attributes` | Yes, in `database_metadata_schema` | Yes, with `attributes` on `POST /query` | 16 KiB | | Custom attributes | `custom_attributes` | No | No, `attributes` cannot filter them | 1 KiB | @@ -45,12 +45,12 @@ A query then filters on the declared fields: | --- | --- | --- | | Scope most queries by a field like department, region, plan, customer or status | `attributes` | Declare the field in `database_metadata_schema` and filter with `"attributes": { "department": "legal" }`. | | Filter by a number or a date range | `attributes` | Store a number, or a date string in one fixed format such as `YYYY-MM-DD`, and filter with `$gt`, `$gte`, `$lt`, `$lte`. | -| Keep source details like author, a Slack timestamp, an external ID or a document version | `custom_attributes` | No schema needed. Stored with the item, not filterable with `attributes`. | +| Keep source details like author, a Slack timestamp, an external ID or a document version | `custom_attributes` | No schema needed. Stored with the context, not filterable with `attributes`. | | Combine a hard scope with semantic search | `attributes` | Send the filter plus your natural-language `query`. The filter narrows the candidates; ranking still uses the query. | | Search semantically over a text attribute | `attributes`, on a `VARCHAR` field with `enable_dense_embedding: true` | Put the concept in `query`. Do not put fuzzy concepts in the filter. | | Search by keyword over a text attribute | `attributes`, on a `VARCHAR` field with `enable_sparse_embedding: true` | Normal `/query` keyword (BM25) matching covers it. | | Partition by user, workspace or team | `collection` | Send `collection` on every request, and filter with `attributes` inside that partition, not as a replacement for it. | -| Restrict who may retrieve an item | `acl` | An attribute filter is not a permission. See [Access control](/essentials/v2/access-control). | +| Restrict who may retrieve a context | `acl` | An attribute filter is not a permission. See [Access control](/essentials/v2/access-control). | The `database` field was formerly `tenant_id` and `collection` was formerly `sub_tenant_id`; the old names still work as deprecated aliases. See [when to use `database` and `collection`](/essentials/v2/databases-and-collections#2-when-to-use-each). @@ -58,7 +58,7 @@ The `database` field was formerly `tenant_id` and `collection` was formerly `sub ## 2. Declare the schema -The schema lives on the database; values land on each item at ingest. Declare the schema when you create the database, before any ingest. +The schema lives on the database; values land on each context at ingest. Declare the schema when you create the database, before any ingest. ```bash cURL @@ -178,7 +178,7 @@ The update is additive only: ## 3. Attach attributes at ingest -Send `attributes` and `custom_attributes` on each entry in `context` on [`POST /context/ingest`](/essentials/v2/ingest). The SDKs send the same array, as a JSON string, in the `context` form field; keys inside each item stay snake_case in every language. +Send `attributes` and `custom_attributes` on each entry in `context` on [`POST /context/ingest`](/essentials/v2/ingest). The SDKs send the same array, as a JSON string, in the `context` form field; keys inside each context stay snake_case in every language. ```bash cURL @@ -257,13 +257,13 @@ Rules checked before anything is queued: - When the database has a schema, every `attributes` key must be declared in it, and every value must match the declared type: a string for `VARCHAR`, `true` or `false` for `BOOL`, a whole number for the integer types, a number for `FLOAT` and `DOUBLE`, an object for `JSON`. `null` is accepted for any declared field. An undeclared key or a wrong type rejects the request with `400`. - `custom_attributes` take any keys, with no schema. - In both maps, keys must not start with `_`, must not be a reserved system name, and must not contain control characters. A value may be a scalar, a list or an object, but not a list or object nested inside another. -- Structural and size errors name the item they refer to, such as `context[0]: ...`. +- Structural and size errors name the context they refer to, such as `context[0]: ...`. -**Attributes are set at ingest.** The `attributes` query filter runs against the values indexed with the item. To change them, re-ingest the item with `upsert: true` and the same `context_id`, which replaces the item. See [IDs and replacement](/essentials/v2/ingest#12-ids-and-replacement). +**Attributes are set at ingest.** The `attributes` query filter runs against the values indexed with the context. To change them, re-ingest the context with `upsert: true` and the same `context_id`, which replaces the context. See [IDs and replacement](/essentials/v2/ingest#12-ids-and-replacement). ### Size limits -Every item is checked against two caps: +Every context is checked against two caps: | Map | Cap | | --- | --- | @@ -282,7 +282,7 @@ The cap applies to the **whole map**, not to any one value, and it is measured o {"deck":"Q3 Board Deck","author":"ada@example.com","summary":"<950 characters>"} ``` -Exceeding either cap fails the whole request with `400 INVALID_INPUT` before anything is ingested. The message names the item and the map, and reports both numbers, so you can see exactly how far over you are: +Exceeding either cap fails the whole request with `400 INVALID_INPUT` before anything is ingested. The message names the context and the map, and reports both numbers, so you can see exactly how far over you are: ```json { @@ -292,7 +292,7 @@ Exceeding either cap fails the whole request with `400 INVALID_INPUT` before any ``` - If an item needs more than 1 KiB of descriptive detail, put the long text in the item's `text`, where it gets chunked and embedded, and keep `custom_attributes` for short values. + If a context needs more than 1 KiB of descriptive detail, put the long text in the context's `text`, where it gets chunked and embedded, and keep `custom_attributes` for short values. --- @@ -396,7 +396,7 @@ Operators combine and nest: | Empty pieces | An empty object, an empty operator object, or an empty `$and`, `$or`, `$in` or `$nin` array is a `400`, not a filter that matches everything. | | Unknown operators | A `400`. There is no `$contains`, `$regex` or fuzzy operator. | | Nesting | At most 10 levels deep through `$and`, `$or` and `$not`. | -| Graph and forceful relations | The filter applies to chunks, forceful relations and graph paths alike. A graph path that touches an item the filter excludes is removed. | +| Graph and forceful relations | The filter applies to chunks, forceful relations and graph paths alike. A graph path that touches a context the filter excludes is removed. | | No matches | A valid filter that matches nothing returns an empty result. HydraDB never drops or widens the filter to find something. | | Size | At most 500 values in each `$in` or `$nin` list, and 64 KiB for the whole object. See [Filter size limits](#filter-size-limits). | @@ -406,19 +406,19 @@ Operators combine and nest: ### No containment on multi-valued fields -`attributes` compares an item's single stored value. There is no containment operator, so a multi-valued field cannot be matched by "does this item's list include X": +`attributes` compares a context's single stored value. There is no containment operator, so a multi-valued field cannot be matched by "does this context's list include X": - A declared field cannot be an array: `data_type: "array"` is rejected with `400`. - A list sent as the value of a `VARCHAR` attribute is rejected at ingest. - A `JSON` attribute cannot be compared at all (only `$exists` applies). - A string that joins several values, such as `"alpha,beta"`, is one value: `$eq` and `$in` match it only as the whole string. -`$in` runs the other way round: it asks whether the item's one value is among the values you list. +`$in` runs the other way round: it asks whether the context's one value is among the values you list. If you need to select context by one member of a set: - Give each member you filter on its own `BOOL` attribute, such as `"tag_billing": true`, and filter with `{"tag_billing": true}`. This counts against the 32-field limit, so it suits a small, known set. -- If the set is really "who may see this item", use `acl` instead. See [Access control](/essentials/v2/access-control). +- If the set is really "who may see this context", use `acl` instead. See [Access control](/essentials/v2/access-control). ### Filter size limits @@ -459,7 +459,7 @@ attributes filter nests deeper than 10 levels ## 5. Edit values in place -[`PATCH /context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) edits the stored values of one item you know the `context_id` of. This endpoint's body names the two maps `database_metadata`, which edits the item's `attributes`, and `additional_metadata`, which edits its `custom_attributes`: +[`PATCH /context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) edits the stored values of one context you know the `context_id` of. This endpoint's body names the two maps `database_metadata`, which edits the context's `attributes`, and `additional_metadata`, which edits its `custom_attributes`: ```json { @@ -476,16 +476,16 @@ attributes filter nests deeper than 10 levels Behavior: -- The item must already exist. +- The context must already exist. - `collection` is required. - At least one of `database_metadata`, `additional_metadata` or `acl` is required. - The update is a merge: sent keys are inserted or overwritten; omitted keys are preserved. - `database_metadata` is checked against the schema exactly as `attributes` are at ingest, and both maps are held to the same [16 KiB and 1 KiB caps](#size-limits). A rejected edit's message is prefixed with `invalid metadata edit:`. -- The same endpoint accepts `acl` to change who may retrieve the item. Unlike the two maps, `acl` **replaces** rather than merges, and an `acl`-only body is a valid edit. See [Access control](/essentials/v2/access-control). +- The same endpoint accepts `acl` to change who may retrieve the context. Unlike the two maps, `acl` **replaces** rather than merges, and an `acl`-only body is a valid edit. See [Access control](/essentials/v2/access-control). - If an edited attribute has `enable_dense_embedding` or `enable_sparse_embedding`, HydraDB updates its search index synchronously and reports `vector_sync_required` / `vector_synced` in the response. A `null` for such a field is rejected. - An edit here is not guaranteed to change what the `attributes` query filter sees, because the filter runs against the values indexed at ingest. To change a value you filter on, re-ingest the item with `upsert: true` and the same `context_id`. + An edit here is not guaranteed to change what the `attributes` query filter sees, because the filter runs against the values indexed at ingest. To change a value you filter on, re-ingest the context with `upsert: true` and the same `context_id`. --- @@ -504,13 +504,13 @@ To page through context rather than run retrieval, use [`POST /context/list`](/a | Ingest returns `400` naming an undeclared field | An `attributes` key is not in the schema | Declare the field, or move it to `custom_attributes` if you never filter on it. | | A filter on a custom attribute is rejected | `attributes` cannot filter on `custom_attributes` | Declare the field, send it in `attributes`, and re-ingest. | | `400 value for "priority" does not match its type` | The operand's JSON type differs from the declared type, such as `"7"` for an `INT64` field | Send the declared type: `{"priority": 7}`. | -| `$in` does not find an item whose field holds several values | There is no containment | See [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | +| `$in` does not find a context whose field holds several values | There is no containment | See [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | | `$ne` or `$not` drops context that has no value for the field | Missing values never match a comparison | Add `{"field": {"$exists": false}}` under `$or`. | | Query returns 0 results after adding a filter | Over-scoping: the combined constraints exclude everything, or the context predates the field | Start with one constraint, add the others one at a time, and check with `$exists`. | -| A value edited with `PATCH /context/{id}/metadata` still filters as the old value | The filter runs against the values indexed at ingest | Re-ingest the item with `upsert: true` and the same `context_id`. | +| A value edited with `PATCH /context/{id}/metadata` still filters as the old value | The filter runs against the values indexed at ingest | Re-ingest the context with `upsert: true` and the same `context_id`. | | A schema field cannot be changed | Declared fields are immutable | Add a new field, or create a new database with the corrected schema and re-ingest. | | Adding a field with `enable_dense_embedding` or `enable_sparse_embedding` returns `400` | Embedding flags can only be set at database creation | Create a new database with the final schema and re-ingest. | -| Ingest or edit returns `400 ... is too large` | Over the 16 KiB `attributes` or 1 KiB `custom_attributes` cap | Trim the map; move long text into the item's `text`. See [Size limits](#size-limits). | +| Ingest or edit returns `400 ... is too large` | Over the 16 KiB `attributes` or 1 KiB `custom_attributes` cap | Trim the map; move long text into the context's `text`. See [Size limits](#size-limits). | | An edit returns `400` | Unknown key, wrong type, over-size map, too-deep nesting, reserved key, or missing `collection` | Check the schema and the [size limits](#size-limits). | | A dense or sparse attribute edit rejects `null` | A null would leave stale search vectors | Set a non-null value, or re-ingest with the desired value. | @@ -520,7 +520,7 @@ To page through context rather than run retrieval, use [`POST /context/list`](/a **Stacked scopes with collection partitioning.** Use `collection` for the partition (per user, per workspace), and use `attributes` to scope *inside* that partition. They are complementary, not interchangeable. See [Databases and collections](/essentials/v2/databases-and-collections). -**Published versus draft.** Declare a `status` field; tag every item with `"attributes": { "status": "draft" }` or `"published"`; pass `"attributes": { "status": "published" }` on user-facing queries. Work in progress stays out of customer answers automatically. +**Published versus draft.** Declare a `status` field; tag every context with `"attributes": { "status": "draft" }` or `"published"`; pass `"attributes": { "status": "published" }` on user-facing queries. Work in progress stays out of customer answers automatically. **Multi-language corpora.** Declare a `language` field and route each query to the right language by passing `"attributes": { "language": "" }`. @@ -535,9 +535,9 @@ To page through context rather than run retrieval, use [`POST /context/list`](/a - [Ingest context](/essentials/v2/ingest): every context field, including `attributes` and `custom_attributes` - [Query](/essentials/v2/query): how `attributes` sits alongside ranking, graph and forceful relations - [Databases and collections](/essentials/v2/databases-and-collections): partitioning versus filtering -- [Access control](/essentials/v2/access-control): restricting who may retrieve an item, which is not an attribute filter +- [Access control](/essentials/v2/access-control): restricting who may retrieve a context, which is not an attribute filter - [Create Database API reference](/api-reference/v2/endpoint/create-tenant): the full `database_metadata_schema` reference -- [Ingest API reference](/api-reference/v2/endpoint/ingest-context): the full item reference +- [Ingest API reference](/api-reference/v2/endpoint/ingest-context): the full field reference - [Query API reference](/api-reference/v2/endpoint/query): the full `attributes` request reference - [List Context](/api-reference/v2/endpoint/list-documents): browsing context with exact-match filters - [Update Source Metadata](/api-reference/v2/endpoint/update-source-metadata): in-place value edits diff --git a/essentials/v2/bring-your-own-graph.mdx b/essentials/v2/bring-your-own-graph.mdx index 1ceffb85..feecc9fb 100644 --- a/essentials/v2/bring-your-own-graph.mdx +++ b/essentials/v2/bring-your-own-graph.mdx @@ -1,13 +1,13 @@ --- title: "Bring Your Own Graph" -description: "Supply your own entities and relations for a context item and skip LLM graph extraction." +description: "Supply your own entities and relations for a context and skip LLM graph extraction." --- ## 1. What it is -Bring Your Own Graph (BYOG) lets you attach your own entities and relations to a context item on [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context), with the request-level `graph_payload` field. For that item, HydraDB **uses your graph instead of running LLM graph extraction**. +Bring Your Own Graph (BYOG) lets you attach your own entities and relations to a context on [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context), with the request-level `graph_payload` field. For that context, HydraDB **uses your graph instead of running LLM graph extraction**. -Your graph is stored in the same shape extraction produces (`source → relation → target` triplets), so it answers queries the same way: its relations come back in `graph[]` on [`POST /query`](/essentials/v2/query) and point at the item's chunks. No query-side changes are needed. +Your graph is stored in the same shape extraction produces (`source → relation → target` triplets), so it answers queries the same way: its relations come back in `graph[]` on [`POST /query`](/essentials/v2/query) and point at the context's chunks. No query-side changes are needed. --- @@ -17,7 +17,7 @@ Use BYOG when you already know the relationships and want them used verbatim: - You maintain a curated knowledge graph, an ontology, or a database export and want those exact facts in HydraDB. - You need deterministic, reproducible relations rather than model-extracted ones. -- You want faster ingestion: a BYOG item skips the graph-extraction LLM call entirely. +- You want faster ingestion: a BYOG context skips the graph-extraction LLM call entirely. Pick the right tool: @@ -25,7 +25,7 @@ Pick the right tool: | --- | --- | | HydraDB to discover relationships for you | [Context graphs](/essentials/v2/context-graphs) (auto-extraction, the default) | | To declare links **between whole contexts** | `forceful_relations` on a context. See [Declared relations](/essentials/v2/ingest#10-declared-relations). | -| To supply the **full entity and relation graph for one item** | **Bring Your Own Graph** (this page) | +| To supply the **full entity and relation graph for one context** | **Bring Your Own Graph** (this page) | | A standalone property graph you write and read with **Cypher**, separate from ingested context | [Cypher Graph Collections](/essentials/v2/graph-collections-byog) | --- @@ -60,11 +60,11 @@ Pick the right tool: } ``` -- **Top-level key:** the `context_id` of an item in the same request. Every key must match one; a key that matches nothing is a `400`, so a typo cannot silently drop a graph. An item needs an explicit `context_id` to receive a graph (an item whose id is generated cannot be targeted). +- **Top-level key:** the `context_id` of a context in the same request. Every key must match one; a key that matches nothing is a `400`, so a typo cannot silently drop a graph. A context needs an explicit `context_id` to receive a graph (a context whose id is generated cannot be targeted). - **`entities`:** a map keyed by a caller-local id. Each entity has a `name` (required), a `type`, a `namespace`, and an optional `identifier` (an external id, display only). The entity key is only a handle for relations to reference; it is not stored. - **`relations`:** a list of edges. `source` and `target` are keys of the `entities` map, and a key that is not declared there is a `400`. `predicate` is required and is any plain string. `context` and `temporal_details` are optional per relation. - Both `entities` and `relations` must be non-empty. -- **No `chunk_id`:** you never supply chunk ids. HydraDB links your relations to the item's chunks server-side. +- **No `chunk_id`:** you never supply chunk ids. HydraDB links your relations to the context's chunks server-side. - Entity names are **normalized (lowercased)** so they match at query time, just like extracted entities. Entities that no relation references are dropped. In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, where `graph_payload` is a JSON string next to the `context` field; see the [examples](#6-example-several-contexts-in-one-request). @@ -73,10 +73,10 @@ In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, wh ## 4. How it behaves -- **Replace mode.** A BYOG item's graph is your `graph_payload`; LLM graph extraction is skipped for it. The item is still chunked and embedded, so it stays fully searchable. -- **Chunk linking.** Each relation is linked to the item's most relevant chunk, so the relation's `chunk_id` in `graph[]` points at the right passage. Linking is permissive (see [Limitations](#7-limitations)). +- **Replace mode.** A BYOG context's graph is your `graph_payload`; LLM graph extraction is skipped for it. The context is still chunked and embedded, so it stays fully searchable. +- **Chunk linking.** Each relation is linked to the context's most relevant chunk, so the relation's `chunk_id` in `graph[]` points at the right passage. Linking is permissive (see [Limitations](#7-limitations)). - **Queryable like any graph.** Your relations come back in `graph[]` on `POST /query` and traverse exactly like extracted ones. See [Context graphs](/essentials/v2/context-graphs). -- **Durable across re-ingest.** Your graph is stored server-side with the item, so it outlives a single request. Re-ingesting the same `context_id` **without** a `graph_payload` (for example, to update its text) re-applies your stored graph: HydraDB does **not** fall back to LLM extraction and does **not** error. To change the graph, re-ingest **with** a new `graph_payload`; it replaces the stored copy. Deleting the item removes its stored graph too. +- **Durable across re-ingest.** Your graph is stored server-side with the context, so it outlives a single request. Re-ingesting the same `context_id` **without** a `graph_payload` (for example, to update its text) re-applies your stored graph: HydraDB does **not** fall back to LLM extraction and does **not** error. To change the graph, re-ingest **with** a new `graph_payload`; it replaces the stored copy. Deleting the context removes its stored graph too. --- @@ -86,8 +86,8 @@ In a JSON body, `graph_payload` is an object. The SDKs send a multipart form, wh | Limit | Value | | --- | --- | -| Entities per item | ≤ 5,000 | -| Relations per item | ≤ 10,000 | +| Entities per context | ≤ 5,000 | +| Relations per context | ≤ 10,000 | | Relations per entity (degree) | ≤ 500 | | Relation `context` length | ≤ 2,000 bytes (UTF-8) | | Entity key, `name`, `type`, `namespace`, `identifier`, `predicate` and `temporal_details` length | ≤ 256 bytes (UTF-8) each | @@ -253,9 +253,9 @@ await client.context.ingest({ -Keys inside each item and each graph stay `snake_case` in every language (`context_id`, `temporal_details`); only the SDK's own arguments follow the language's casing. +Keys inside each context and each graph stay `snake_case` in every language (`context_id`, `temporal_details`); only the SDK's own arguments follow the language's casing. -Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) until each item reaches `completed`, so its graph is written, then query. `graph_context` is on by default: +Poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) until each context reaches `completed`, so its graph is written, then query. `graph_context` is on by default: ```bash cURL curl -X POST 'https://api.hydradb.com/query' \ @@ -301,9 +301,9 @@ The same facts also appear under `## Related facts` in `llm_prompt`. See [Query] ## 7. Limitations -- **Replace, not augment.** A BYOG item has no LLM-extracted graph facts, only the graph you supply (plus normal chunk search). Augment mode is a future enhancement. -- **Permissive linking can produce false positives.** Every relation links to its best-matching chunk even if the match is weak; there is no reject floor yet. A linked relation is **sourced** (similar to a chunk), not necessarily **supported** (stated by the item). -- **Bulk, one-shot.** You supply the whole graph with the item. Per-triple add, update and delete are not yet available. +- **Replace, not augment.** A BYOG context has no LLM-extracted graph facts, only the graph you supply (plus normal chunk search). Augment mode is a future enhancement. +- **Permissive linking can produce false positives.** Every relation links to its best-matching chunk even if the match is weak; there is no reject floor yet. A linked relation is **sourced** (similar to a chunk), not necessarily **supported** (stated by the context). +- **Bulk, one-shot.** You supply the whole graph with the context. Per-triple add, update and delete are not yet available. --- diff --git a/essentials/v2/context-categories.mdx b/essentials/v2/context-categories.mdx index 613ae1bb..6d8934ef 100644 --- a/essentials/v2/context-categories.mdx +++ b/essentials/v2/context-categories.mdx @@ -1,10 +1,10 @@ --- title: "Context categories" -description: "Label each item as a user preference, business knowledge or a decision trace. The label is yours to set; nothing infers it." +description: "Label each context as a user preference, business knowledge or a decision trace. The label is yours to set; nothing infers it." noindex: true --- -A **context category** says what kind of context an item is. You set it per item with `context_category` on [`POST /context/ingest`](/essentials/v2/ingest). Every category lives in the same database and is searched by the same [query](/essentials/v2/query); the category describes the item, it does not decide where it is stored. +A **context category** says what kind of content a context holds. You set it per context with `context_category` on [`POST /context/ingest`](/essentials/v2/ingest). Every category lives in the same database and is searched by the same [query](/essentials/v2/query); the category describes the context, it does not decide where it is stored. | Category | What it holds | Typical source | Usually stored in | | --- | --- | --- | --- | @@ -23,9 +23,9 @@ A database holds three kinds of context: - **Business knowledge:** what your company knows. - **Decision traces:** what your agents and teams decided, and why. -Labelling an item tells HydraDB which of these it is, so enrichment extracts the right things from it. A conversation labelled `user_preference` is read for preferences; a postmortem labelled `decision_trace` is read for the decision, its outcome and the evidence behind it. +Labelling a context tells HydraDB which of these it is, so enrichment extracts the right things from it. A conversation labelled `user_preference` is read for preferences; a postmortem labelled `decision_trace` is read for the decision, its outcome and the evidence behind it. -The label is yours. HydraDB never infers a category and never relabels one you set. The value is validated strictly: a misspelling is a `400`, never an item filed under nothing. On query, the label comes back as `enrichment_kind` on the item's chunks. +The label is yours. HydraDB never infers a category and never relabels one you set. The value is validated strictly: a misspelling is a `400`, never a context filed under nothing. On query, the label comes back as `enrichment_kind` on the context's chunks. --- @@ -188,7 +188,7 @@ A decision trace becomes part of the [context graph](/essentials/v2/context-grap ## 5. `auto` -Leave `context_category` out, or send `auto`, and the item carries no label: it is stored and enriched as general context, and its chunks come back without `enrichment_kind`. HydraDB does not classify the item for you. +Leave `context_category` out, or send `auto`, and the context carries no label: it is stored and enriched as general context, and its chunks come back without `enrichment_kind`. HydraDB does not classify the context for you. Use it for context that is none of the three kinds, or when you genuinely do not know. Pin a category whenever you do: a pinned category is never relabelled, and it tells enrichment exactly what to look for. @@ -196,7 +196,7 @@ Use it for context that is none of the three kinds, or when you genuinely do not ## 6. Changing a category -Re-ingest the item with the same `context_id` and the new `context_category`. Ingest replaces the previous version, so the item is re-enriched under its new category. +Re-ingest the context with the same `context_id` and the new `context_category`. Ingest replaces the previous version, so the context is re-enriched under its new category. --- @@ -218,7 +218,7 @@ One request can carry any mix of categories: } ``` -The last item has no category, so it is stored as general context. +The last context has no category, so it is stored as general context. ### Preferences per person, knowledge shared diff --git a/essentials/v2/context-graphs.mdx b/essentials/v2/context-graphs.mdx index 613ee3c9..2ebd3128 100644 --- a/essentials/v2/context-graphs.mdx +++ b/essentials/v2/context-graphs.mdx @@ -201,4 +201,4 @@ Inject `llm_prompt` and the model can reason over the paths and cite them. See [ - [Ingest context](/essentials/v2/ingest): `enrich`, `forceful_relations` and `graph_payload` - [Bring Your Own Graph](/essentials/v2/bring-your-own-graph): supply your own entities and relations instead of auto-extraction - [How to Use API Results](/essentials/v2/api-results): the `## Related facts` section of `llm_prompt` -- [Connected Subgraph](/api-reference/v2/endpoint/subgraph): everything connected to one item, walked breadth-first +- [Connected Subgraph](/api-reference/v2/endpoint/subgraph): everything connected to one context, walked breadth-first diff --git a/essentials/v2/databases-and-collections.mdx b/essentials/v2/databases-and-collections.mdx index 117ece61..b40f0ea5 100644 --- a/essentials/v2/databases-and-collections.mdx +++ b/essentials/v2/databases-and-collections.mdx @@ -103,7 +103,7 @@ Use `collection` when the data belongs to a specific user, workspace, team, or o Examples: -- An item about John's preferences should be written with John's `collection`. +- A context about John's preferences should be written with John's `collection`. - Workspace-specific runbooks should be written with that workspace's `collection`. - Broadly shared context should use the same scope you plan to use when querying it. diff --git a/essentials/v2/graph-collections-byog.mdx b/essentials/v2/graph-collections-byog.mdx index 24f9e74e..b80f9d16 100644 --- a/essentials/v2/graph-collections-byog.mdx +++ b/essentials/v2/graph-collections-byog.mdx @@ -12,7 +12,7 @@ their Cypher and their data model as they are. Cypher Graph Collections are separate from ingested context. The endpoints live under the `/byog` path, but they are not [Bring Your Own Graph](/essentials/v2/bring-your-own-graph), - which attaches your own entities and relations to a context item at ingest + which attaches your own entities and relations to a context at ingest with `graph_payload`. diff --git a/essentials/v2/split-databases.mdx b/essentials/v2/split-databases.mdx index c2ff6c19..af8c2bcd 100644 --- a/essentials/v2/split-databases.mdx +++ b/essentials/v2/split-databases.mdx @@ -6,7 +6,7 @@ noindex: true This page is deprecated: it documents the knowledge and memory API, which unified databases replace. See [Ingest context](/essentials/v2/ingest) and [Query](/essentials/v2/query). -Every other page in these docs describes a **unified** database: you send `context` items and query without a corpus selector. This page covers the older model, for integrations that still use it: +Every other page in these docs describes a **unified** database: you send a `context` list and query without a corpus selector. This page covers the older model, for integrations that still use it: - databases created with `type: "split"`, including every database created before unified became the default - the older field names HydraDB still accepts on any database @@ -19,7 +19,7 @@ Nothing on this page is going away. Existing databases keep working unchanged an | Type | How you get one | What it holds | | --- | --- | --- | -| `unified` | `POST /databases` without `type` (the default) | One corpus. Send `context` items; query without `type`. | +| `unified` | `POST /databases` without `type` (the default) | One corpus. Send a `context` list; query without `type`. | | `split` | `POST /databases` with `"type": "split"`, or any database created before unified became the default | Two corpora, knowledge and memory, selected with `type` on every context and query call. | ```bash @@ -35,7 +35,7 @@ To find out which type a database is: - `GET /databases` returns `details: [{ "database": "acme", "type": "unified" }, ...]`. When `type` is absent, treat the database as split. - `GET /databases/status?database=acme` returns `type` alongside the readiness flags. -The type is fixed when the database is created. There is no conversion: to move a split database to unified, create a new database, re-ingest your content as items, and point your client at it. A client detects the layout once per database and caches it; it never branches on a request flag. +The type is fixed when the database is created. There is no conversion: to move a split database to unified, create a new database, re-ingest your content as contexts, and point your client at it. A client detects the layout once per database and caches it; it never branches on a request flag. --- @@ -71,42 +71,42 @@ Relations, subgraph and chunks read one corpus at a time, so `all` reads knowled `DELETE /context` with `type: "all"` deletes from **both** corpora. The two deletes are not atomic: if the knowledge delete succeeds and the memory delete then fails, the knowledge deletions have already happened. To delete from one corpus only, send `knowledge` or `memory`. -An ingest that carries `context` items on a split database writes to its **memory** corpus, because items are memory-shaped. Absent and `type: "memory"` both land there; `type: "knowledge"` together with `context` is a `400`. +An ingest that carries a `context` list on a split database writes to its **memory** corpus, because contexts are memory-shaped. Absent and `type: "memory"` both land there; `type: "knowledge"` together with `context` is a `400`. --- ## 3. Split ingest fields -A split database also accepts the ingest shapes that predate items. Each has its own page, kept live but out of the navigation: +A split database also accepts the ingest shapes that predate the `context` list. Each has its own page, kept live but out of the navigation: | Field | What it ingests | Page | | --- | --- | --- | | `documents` + `document_metadata` | Files (PDF, DOCX, Markdown, CSV and more), with per-file metadata | [Knowledge](/essentials/v2/knowledge) | | `app_knowledge` | Structured app sources (Slack threads, tickets, pages) | [App sources](/essentials/v2/app-sources) | -| `memories` | Memory items: `text` or `user_assistant_pairs`, with `infer`, `expiry_time` and `relations` | [Memories](/essentials/v2/memories) | +| `memories` | Memory entries: `text` or `user_assistant_pairs`, with `infer`, `expiry_time` and `relations` | [Memories](/essentials/v2/memories) | | `graph_payload` | A graph you built yourself | [Bring your own graph](/essentials/v2/bring-your-own-graph) | -A unified database rejects `type`, `documents`, `app_knowledge` and `memories` with a `400` naming `context`. It takes text only, so extract text from files and send it as an item. `graph_payload` works on both, keyed by `context_id` on a unified database. +A unified database rejects `type`, `documents`, `app_knowledge` and `memories` with a `400` naming `context`. It takes text only, so extract text from files and send it as a context. `graph_payload` works on both, keyed by `context_id` on a unified database. -### Mapping split fields to items +### Mapping split fields to context fields -| Split field | Item field | +| Split field | Context field | | --- | --- | | `memories[]`, `app_knowledge[]`, `documents` | `context[]` | | `id` / `source_id` | `context_id` | | `text` (memory), `content.text` (app knowledge) | `text` | | `user_assistant_pairs` | `conversation`, as `[{ role, content }]` | | `infer` (default `false`) | `enrich` (default `true`) | -| `custom_instructions` | `instructions`, on the item or on the request | +| `custom_instructions` | `instructions`, on the context or on the request | | `observation_date` | `happened_at` | | `metadata` | `attributes` | | `additional_metadata` | `custom_attributes` | -| `upsert` (request only) | `upsert` on the item, with the request value as the default | -| `relations` (knowledge only) | `forceful_relations`, on any item | -| `acl` (app sources only) | `acl`, on any item | +| `upsert` (request only) | `upsert` on the context, with the request value as the default | +| `relations` (knowledge only) | `forceful_relations`, on any context | +| `acl` (app sources only) | `acl`, on any context | | `is_markdown`, `evidence_kind`, `evidence_subject`, `expiry_time`, `retain_source` | removed; a `400` on a unified database | -No split name is accepted on a unified database: send the item-field names. An unknown key, on the request, on an item, on a conversation turn or inside `forceful_relations`, is a `400` that names the key. The full item reference is on [Ingest context](/essentials/v2/ingest#3-item-fields). +No split name is accepted on a unified database: send the context field names. An unknown key, on the request, on a context, on a conversation turn or inside `forceful_relations`, is a `400` that names the key. The full context reference is on [Ingest context](/essentials/v2/ingest#3-context-fields). --- @@ -220,17 +220,17 @@ In the **indexing webhook payload**, `tenant_id` and `database` do not carry the --- -## 6. Names that differ between items and other responses +## 6. Names that differ between context and other responses `POST /query` on a unified database returns `context_id` on every chunk and no attributes at all. The context management endpoints keep their existing response shapes, so a few things are spelled differently on the way in and on the way out there: -| On an item | On `POST /context/list`, `GET /context/inspect` and `PATCH /context/{id}/metadata` | +| On a context | On `POST /context/list`, `GET /context/inspect` and `PATCH /context/{id}/metadata` | | --- | --- | | `context_id` | `id` | | `attributes` | `metadata` (`database_metadata` on the PATCH body) | | `custom_attributes` | `additional_metadata` | -- The ingest `202` also reports each item as `results[].source_id`; read it as the `context_id`. +- The ingest `202` also reports each context as `results[].source_id`; read it as the `context_id`. - `content` is accepted on ingest as an alias of `text`, and `messages` as an alias of `conversation`. - On a unified database, `GET /databases/stats` reports the database's indexed chunk count in `knowledge_collection.row_count`, and `memory_collection` repeats the same number. diff --git a/essentials/v2/webhooks.mdx b/essentials/v2/webhooks.mdx index a1c47d4b..d1c4ae57 100644 --- a/essentials/v2/webhooks.mdx +++ b/essentials/v2/webhooks.mdx @@ -3,7 +3,7 @@ title: "Webhooks" description: "Receive indexing status events when ingested content finishes processing." --- -Webhooks let your application receive an HTTP callback when HydraDB finishes processing an ingested item. +Webhooks let your application receive an HTTP callback when HydraDB finishes processing an ingested context. Use them when you want to: @@ -20,7 +20,7 @@ Webhooks are sent for terminal indexing states. For progress updates before comp ## 1. How it works -When an ingested item reaches a terminal state, HydraDB creates a delivery record and sends a `POST` request to your webhook URL. +When an ingested context reaches a terminal state, HydraDB creates a delivery record and sends a `POST` request to your webhook URL. ```mermaid flowchart LR @@ -42,7 +42,7 @@ The supported event today is: | Event | When it fires | |---|---| -| `indexing.status_changed` | When an item reaches `completed`, `errored`, or `success` | +| `indexing.status_changed` | When a context reaches `completed`, `errored`, or `success` | `success` is a legacy alias for `completed`. @@ -313,9 +313,9 @@ For failed indexing, the payload can include `error_code` and `error_message`: |---|---| | `event` | Event type. Currently `indexing.status_changed`. | | `delivery_id` | Stable ID for this event. Store it to deduplicate retries. | -| `id` | The item's `context_id`: the one you supplied at ingestion, or the generated one. For connector-synced content, the connector item's id. | +| `id` | The context's `context_id`: the one you supplied at ingestion, or the generated one. For connector-synced content, the connector context's id. | | `database` | The name of the database you ingested into: the value you sent as `database` (or `tenant_id`) on the ingest request. Empty only for context ingested before this field existed. | -| `collection` | Collection scope for the indexed item. | +| `collection` | Collection scope for the indexed context. | | `status` | Terminal indexing status. Usually `completed` or `errored`. | | `timestamp` | Time the webhook payload was created. | | `error_code` | Present when available for failed processing. | @@ -616,7 +616,7 @@ curl 'https://api.hydradb.com/webhooks/indexing/deliveries?limit=20' \ } ``` -Delivery history calls the item's id `doc_id`. The outbound webhook payload calls it `id`. +Delivery history calls the context's id `doc_id`. The outbound webhook payload calls it `id`. ### Filter deliveries @@ -786,4 +786,4 @@ The overlap lives in your receiver, not in HydraDB. Each delivery carries a sing | Signatures started failing after a rotation | Rotation applies immediately. Confirm your receiver has the new secret deployed, and see [Zero-downtime key rotation](#zero-downtime-key-rotation) to avoid the gap next time. | | Event arrives more than once | This is expected during retries. Deduplicate with `delivery_id`. | | Event never arrives | Check the dashboard delivery history for `failed` or `permanently_failed`. | -| `id` is unexpected | It is the item's `context_id` (the one you supplied, or the generated one), or the connector item's id for synced content. | +| `id` is unexpected | It is the context's `context_id` (the one you supplied, or the generated one), or the connector context's id for synced content. | diff --git a/get-started/v2/core-concepts.mdx b/get-started/v2/core-concepts.mdx index 71b79e9d..73800562 100644 --- a/get-started/v2/core-concepts.mdx +++ b/get-started/v2/core-concepts.mdx @@ -10,7 +10,7 @@ description: "A tour of the primitives that make HydraDB: databases and collecti | **Query** | One endpoint that reads context back, personalized with weighted collections | [Query](/essentials/v2/query) | | **Attributes** | Declared fields you filter on, for deterministic retrieval | [Attributes](/essentials/v2/attributes) | | **Context graph** | Entities, relations and decisions extracted from everything you ingest | [Context graphs](/essentials/v2/context-graphs) | -| **Access control** | Who may retrieve each item | [Access control](/essentials/v2/access-control) | +| **Access control** | Who may retrieve each context | [Access control](/essentials/v2/access-control) | --- @@ -89,7 +89,7 @@ A **collection** is a partition within a database: a user, team, project or depa - **B2C:** one database for your app. Each end user gets a collection, and shared content lives in a `company` collection. - **B2B:** each customer is a database. Their teams or departments are collections. -Collections separate data; they are the right tool when context must never mix. When the question is who may see an item inside a shared collection, use [access control](#access-control) instead. +Collections separate data; they are the right tool when context must never mix. When the question is who may see a context inside a shared collection, use [access control](#access-control) instead. Read more: [Databases and collections](/essentials/v2/databases-and-collections) @@ -100,7 +100,7 @@ Read more: [Databases and collections](/essentials/v2/databases-and-collections) Attributes make retrieval deterministic. Production systems often need hard filters: "only Engineering docs", "only approved policies". - `attributes`: fields you declare in the database's `database_metadata_schema` and filter on at query time. -- `custom_attributes`: free-form fields stored with an item. Not filterable with `attributes`. +- `custom_attributes`: free-form fields stored with a context. Not filterable with `attributes`. At ingest: @@ -135,7 +135,7 @@ Read more: [Context graphs](/essentials/v2/context-graphs) ## Access control -Set `acl` on an item to restrict who may retrieve it, and pass the caller's principals on query. An item with no `acl` is visible to everyone who can query the collection. +Set `acl` on a context to restrict who may retrieve it, and pass the caller's principals on query. A context with no `acl` is visible to everyone who can query the collection. Read more: [Access control](/essentials/v2/access-control) diff --git a/plugins/claude-code.mdx b/plugins/claude-code.mdx index 5fc438eb..5112ee34 100644 --- a/plugins/claude-code.mdx +++ b/plugins/claude-code.mdx @@ -98,10 +98,10 @@ The plugin talks to your database through two endpoints. - **Recall.** Before each prompt, it sends the prompt text to `POST /query`, with `mode` from `recallMode`, `graph_context` from `graphContext`, `follow_forceful_relations` from `followForcefulRelations`, and `max_results` set to `maxMemoryResults + maxKnowledgeResults` (10 by default). It injects the server-built `llm_prompt` verbatim, and whole, inside a `` block: ranked results, forceful relations, related facts from the context graph, temporal facts and sources, labelled `[1]`, `[R1]` and `[P1]` for citation. - **Capture.** Conversations, notes and workspace docs are sent to `POST /context/ingest` in the `context` list with enrichment on: - - `turn` capture sends each exchange as a `conversation` item, with your turns named after `userName` when it is set. - - `session-upsert` capture keeps one `text` item per session, holding the session transcript, and replaces it after each response. - - `/hydradb:ingest --note` sends the note as a `text` item. - - Workspace sync sends each matching file as a `text` item titled with its relative path. A changed file replaces its item, and a full sync removes the item of a deleted or excluded file. + - `turn` capture sends each exchange as a `conversation` context, with your turns named after `userName` when it is set. + - `session-upsert` capture keeps one `text` context per session, holding the session transcript, and replaces it after each response. + - `/hydradb:ingest --note` sends the note as a `text` context. + - Workspace sync sends each matching file as a `text` context titled with its relative path. A changed file replaces its context, and a full sync removes the context of a deleted or excluded file. `memoryCustomInstructions` steers enrichment for conversations, sessions and notes, and `workspaceMemoryCustomInstructions` for workspace docs. Secret-looking content is redacted before anything leaves the workspace. @@ -132,7 +132,7 @@ The earlier names `/hydradb:status`, `/hydradb:search`, `/hydradb:remember`, `/h | Value | Behavior | | ---------------- | ------------------------------------------------------------------------------------------- | | `session-upsert` | **(default)** Maintains one evolving session transcript, upserted after each response | -| `turn` | Saves each user/assistant exchange as its own conversation item | +| `turn` | Saves each user/assistant exchange as its own conversation context | | `both` | Saves individual turns and a rolling session transcript | | `off` | No automatic saves; manual saves still work via `/hydradb:ingest --session` | @@ -178,7 +178,7 @@ The earlier names `/hydradb:status`, `/hydradb:search`, `/hydradb:remember`, `/h | `maxKnowledgeResults` | `4` | Added to `maxMemoryResults`, as above | | `maxFileSizeBytes` | `52428800` (50 MB) | Max file size for workspace sync | | `maxFilesPerSync` | `25` | Max files synced per pass | -| `maxMemoryCharsPerChunk` | `52428800` | Max characters per synced item; a longer file is split into numbered parts, each its own item | +| `maxMemoryCharsPerChunk` | `52428800` | Max characters per synced context; a longer file is split into numbered parts, each its own context | ### Timeouts diff --git a/plugins/cli.mdx b/plugins/cli.mdx index 099dbfcb..d7800034 100644 --- a/plugins/cli.mdx +++ b/plugins/cli.mdx @@ -165,19 +165,19 @@ has run. | Command | Description | |---|---| | `hydradb query QUERY` | Query the database: the single retrieval entry point | -| `hydradb ingest` | Ingest one text or conversation item | +| `hydradb ingest` | Ingest one text or conversation context | | `hydradb list` | List stored context | -| `hydradb inspect ID` | Fetch an item's content by ID | +| `hydradb inspect ID` | Fetch a context's content by ID | | `hydradb delete IDS...` | Delete context by ID | -| `hydradb relations ID` | Explore context-graph relations for an item | +| `hydradb relations ID` | Explore context-graph relations for a context | | `hydradb verify IDS...` | Check ingestion status per ID | #### Ingesting -`ingest` sends one item to `POST /context/ingest`: exactly one of `--text` (a note or a +`ingest` sends one context to `POST /context/ingest`: exactly one of `--text` (a note or a document's text, or `-` for stdin) or `--conversation-file` (a conversation). The CLI does not upload files: to ingest a document, extract its text and pass it with -`--text`, up to 1 MiB of text per item. +`--text`, up to 1 MiB of text per context. ```bash # Store a note @@ -210,18 +210,18 @@ cat notes.txt | hydradb ingest --title "Meeting notes" --database my-db | `--text`, `-t` | Text to ingest. Use `-` to read from stdin | | `--conversation-file` | Path to a JSON list of `{role, content}` turns (roles `user`, `assistant`, `system`) | | `--title` | Optional title | -| `--context-id` | Caller-assigned ID for the item (generated when omitted) | -| `--enrich` / `--no-enrich` | Extract facts and graph relations for the item (default on) | -| `--instructions` | Steer enrichment for this item | -| `--happened-at` | The date the item is about, `YYYY-MM-DD` | +| `--context-id` | Caller-assigned ID for the context (generated when omitted) | +| `--enrich` / `--no-enrich` | Extract facts and graph relations for the context (default on) | +| `--instructions` | Steer enrichment for this context | +| `--happened-at` | The date the context is about, `YYYY-MM-DD` | | `--attributes` | Declared, filterable attributes as a JSON object (keys from the database's metadata schema) | | `--custom-attributes` | Free-form attributes as a JSON object, not filterable | -| `--forceful-relation` | A context ID this item is declared related to; repeatable | -| `--acl` | A principal allowed to retrieve the item (for example `user_email:a@x.com` or `domain:acme.com`); repeatable | -| `--upsert` / `--no-upsert` | Replace an existing item with the same context ID (default on) | +| `--forceful-relation` | A context ID this context is declared related to; repeatable | +| `--acl` | A principal allowed to retrieve the context (for example `user_email:a@x.com` or `domain:acme.com`); repeatable | +| `--upsert` / `--no-upsert` | Replace an existing context with the same context ID (default on) | -The command prints the queued item's context ID. Pass it to `hydradb verify` to watch -indexing; an item is searchable once it has finished. See +The command prints the queued context's ID. Pass it to `hydradb verify` to watch +indexing; a context is searchable once it has finished. See [Ingest](/essentials/v2/ingest) for every context field. #### Querying @@ -288,7 +288,7 @@ Because matching ignores case, two documents whose names differ only by case, # List what is stored hydradb list --database my-db -# Read one item back +# Read one context back hydradb inspect policy-1 --database my-db # Check indexing progress @@ -298,7 +298,7 @@ hydradb verify policy-1 --database my-db hydradb delete policy-1 --database my-db --yes ``` -`list` lists every item in scope, text and conversations alike, and accepts `--page` +`list` lists every context in scope, text and conversations alike, and accepts `--page` and `--page-size` (1 to 100). `inspect` accepts `--mode content` (default), `url`, or `both`. `list`, `inspect` and `relations` also accept `--acl` to answer as specific principals. Deleting an ID that does not exist exits non-zero rather than reporting @@ -354,7 +354,7 @@ hydradb -o json query "user preferences" --database my-db \ # Hand the server-built prompt to a model hydradb query "user preferences" --llm --database my-db | my-model-call -# Ingest each markdown file as one text item, keyed by its name so a re-run replaces it +# Ingest each markdown file as one text context, keyed by its name so a re-run replaces it for f in ./docs/*.md; do name="$(basename "$f" .md)" hydradb ingest --text - --title "$name" --context-id "doc-$name" --database my-db < "$f" diff --git a/plugins/mcp.mdx b/plugins/mcp.mdx index c45835cd..3f2d119c 100644 --- a/plugins/mcp.mdx +++ b/plugins/mcp.mdx @@ -443,9 +443,9 @@ same scope names the rest of the product uses. See the | Tool | What it does | | ---- | ------------ | | `hydradb_query` | Query the database; returns the server-built `llm_prompt` (ranked results, forceful relations, related facts from the context graph) plus the same answer as structured content | -| `hydradb_ingest` | Store a note or document (`text`) or a conversation (`turns`) as one context item; HydraDB enriches it and adds it to the context graph | +| `hydradb_ingest` | Store a note or document (`text`) or a conversation (`turns`) as one context; HydraDB enriches it and adds it to the context graph | | `hydradb_list` | List what is stored in a collection, one page at a time | -| `hydradb_inspect` | Retrieve the original content of a stored item by ID | +| `hydradb_inspect` | Retrieve the original content of a stored context by ID | | `hydradb_delete` | Remove stored context by ID | | `hydradb_status` | Check whether ingested context has finished indexing | | `hydradb_list_collections` | List collections (sub-tenants) in a database | @@ -465,7 +465,7 @@ Sends the question to `POST /query` and returns the answer described under | `graph_context` | boolean | No | Include related facts from the context graph (`graph[]`) in the answer (default: `true`) | | `follow_forceful_relations` | boolean | No | Also return context declared related at ingest (see `forceful_relations` on `hydradb_ingest`), listed under Forceful relations with `[R1]` labels (default: `true`). They are followed in `thinking` mode | | `operator` | string | No | `or`, `and`, or `phrase`. Switches the query to keyword retrieval, which matches the literal words instead of running hybrid semantic search. Leave unset for normal searches | -| `source_ids` | string[] | No | Restrict the search to these item IDs (context IDs from earlier results or `hydradb_list`). No match returns an empty result | +| `source_ids` | string[] | No | Restrict the search to these context IDs (context IDs from earlier results or `hydradb_list`). No match returns an empty result | | `titles` | string[] | No | Restrict the search to context whose **complete** title exactly matches any value, ignoring case | | `recency_bias` | number | No | Favour recently updated context when ranking, 0-1 (default: `0`). Re-ranks only; it never excludes older context | | `query_apps` | boolean | No | App-aware retrieval over connector content: exact IDs and actors, thread reconstruction, parent and child expansion (default: `false`) | @@ -482,7 +482,7 @@ one-line count of what was found: it is never trimmed or truncated. It is markdo `## Profiles`, `## Code search` and `## Sources`, each only when there is something to show. Results are numbered `1`, `2`; forceful relations (linked by the author at ingest, not matched by the query) `R1`, `R2`; related facts (context-graph paths) `P1`, `P2`. The model cites them as `[1]`, -`[R1]` and `[P1]`. Each entry shows its `**Id:**`, the item's context ID, which +`[R1]` and `[P1]`. Each entry shows its `**Id:**`, the context ID, which `hydradb_inspect` and `hydradb_delete` accept. The result ends with the query's `request_id`. @@ -518,30 +518,30 @@ Because matching ignores case, two documents whose names differ only by case, ### hydradb_ingest -Sends one item to `POST /context/ingest` under the `context` list: a text item for -`text`, or a conversation item (`role` and `content` turns) for `turns`. Provide +Sends one context to `POST /context/ingest` under the `context` list: a text context for +`text`, or a conversation context (`role` and `content` turns) for `turns`. Provide exactly one of `text` or `turns`. Passing both is rejected. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `text` | string | No* | A note, fact, decision, or document body | | `turns` | array | No* | Conversation turns to ingest instead of `text`, each with a `user` and an `assistant` field | -| `title` | string | No | Title for the item, shown next to it in later results | -| `source_id` | string | No | The item's context ID (generated when omitted). Reusing one replaces what is stored under it | +| `title` | string | No | Title for the context, shown next to it in later results | +| `source_id` | string | No | The context ID (generated when omitted). Reusing one replaces what is stored under it | | `overwrite` | boolean | No | Allow that replacement (default: `true`) | -| `infer` | boolean | No | Enrich the item: extract facts and add them to the context graph (default: `true`) | -| `instructions` | string | No | Steers what enrichment extracts from this item; replaces the server's default guidance for it | +| `infer` | boolean | No | Enrich the context: extract facts and add them to the context graph (default: `true`) | +| `instructions` | string | No | Steers what enrichment extracts from this context; replaces the server's default guidance for it | | `is_markdown` | boolean | No | Chunk `text` on its markdown structure (default: `false`) | | `user_name` | string | No | Name of the user, used as the speaker of the user turns in `turns` (default: `User`) | | `happened_at` | string | No | Calendar date `YYYY-MM-DD` when the fact was true, as opposed to when it was stored | | `attributes` | object | No | Declared, filterable key/value attributes (keys from the database's metadata schema). See [Attributes](/essentials/v2/attributes) | -| `custom_attributes` | object | No | Free-form key/value data stored with the item, not filterable | -| `forceful_relations` | string[] | No | Context IDs this item is declared related to, such as the thread or document it belongs to. A later query that returns this item can pull them in under Forceful relations | -| `acl` | string[] | No | Principals that may read the item: an email, a `domain:`, or a `group::`. Omit it for an item anyone holding the key may read | +| `custom_attributes` | object | No | Free-form key/value data stored with the context, not filterable | +| `forceful_relations` | string[] | No | Context IDs this context is declared related to, such as the thread or document it belongs to. A later query that returns this context can pull them in under Forceful relations | +| `acl` | string[] | No | Principals that may read the context: an email, a `domain:`, or a `group::`. Omit it for a context anyone holding the key may read | | `database` | string | No | Database (tenant) scope override for this request | | `collection` | string | No | Collection (sub-tenant) scope override for this request | -Ingestion is asynchronous: an item is not searchable the instant it is saved. Use +Ingestion is asynchronous: a context is not searchable the instant it is saved. Use `hydradb_status` with the returned ID to confirm. ### hydradb_list @@ -562,11 +562,11 @@ connector content appear in one listing. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | string | Yes | The item ID to fetch content for | +| `id` | string | Yes | The context ID to fetch content for | | `mode` | string | No | `content` for text, `url` for a presigned URL, `both` for both (default: `content`) | | `offset` | number | No | Character offset to start reading from | | `limit` | number | No | Maximum characters to return (max `20000`) | -| `acl` | string[] | No | Principals to answer as; an item their access list does not admit is not returned | +| `acl` | string[] | No | Principals to answer as; a context their access list does not admit is not returned | | `database` | string | No | Database (tenant) scope override for this request | | `collection` | string | No | Collection (sub-tenant) scope override for this request | @@ -587,7 +587,7 @@ connector content appear in one listing. ### hydradb_delete_collection -Permanently removes one collection and every item and graph node inside it. The parent database is left intact. +Permanently removes one collection and every context and graph node inside it. The parent database is left intact. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | @@ -598,7 +598,7 @@ Permanently removes one collection and every item and graph node inside it. The | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `ids` | array | Yes | The item IDs to check indexing status for | +| `ids` | array | Yes | The context IDs to check indexing status for | | `database` | string | No | Database (tenant) scope override for this request | | `collection` | string | No | Collection (sub-tenant) scope override for this request | @@ -627,10 +627,10 @@ HydraDB MCP also exposes tools for querying and writing [Cypher Graph Collection - An item cannot be deleted while it is still being indexed. The server + A context cannot be deleted while it is still being indexed. The server refuses with *"Source is still processing; retry deletion after ingestion completes"*, and the tool passes that back rather than reporting a deletion - that did not happen. Retry once `hydradb_status` shows the item has finished. + that did not happen. Retry once `hydradb_status` shows the context has finished. This applies to freshly ingested context only; a context is listable and inspectable before it is deletable. @@ -651,11 +651,11 @@ server-built `llm_prompt` as-is: ranked results, forceful relations, related fac from the context graph, temporal facts and sources, each labelled for citation. The structured answer (`chunks`, `graph`, `forceful_relations`) rides beside it. -At capture time, `hydradb_ingest` sends one item to `POST /context/ingest`: a text -item for a note or document, or a conversation item for `turns`. Enrichment is on -by default, so HydraDB extracts facts from the item and adds them to the context -graph. Reusing an item's ID replaces it, and `hydradb_status` reports when a new -item becomes searchable. +At capture time, `hydradb_ingest` sends one context to `POST /context/ingest`: a text +context for a note or document, or a conversation context for `turns`. Enrichment is on +by default, so HydraDB extracts facts from the context and adds them to the context +graph. Reusing a context's ID replaces it, and `hydradb_status` reports when a new +context becomes searchable. --- diff --git a/plugins/openclaw.mdx b/plugins/openclaw.mdx index bb67bf91..921796ac 100644 --- a/plugins/openclaw.mdx +++ b/plugins/openclaw.mdx @@ -153,7 +153,7 @@ flowchart LR ``` - **Auto-Recall**: Before every AI turn, sends the prompt to `POST /query` (`max_results` from `maxRecallResults`, `mode` from `recallMode`, `graph_context` from `graphContext`, and `follow_forceful_relations` on) and injects the server-built `llm_prompt` verbatim. See [Context Injection](#context-injection). -- **Auto-Capture**: After every AI turn, sends the conversation (the user and assistant turns OpenClaw hands the plugin) to `POST /context/ingest` as one `conversation` item of `role` and `content` turns, with enrichment on and `upsert: true`. The item's `context_id` is derived from the session ID, so each capture replaces the session's earlier item, and HydraDB adds what it extracts to the context graph. +- **Auto-Capture**: After every AI turn, sends the conversation (the user and assistant turns OpenClaw hands the plugin) to `POST /context/ingest` as one `conversation` context of `role` and `content` turns, with enrichment on and `upsert: true`. The context's `context_id` is derived from the session ID, so each capture replaces the session's earlier context, and HydraDB adds what it extracts to the context graph. --- @@ -163,11 +163,11 @@ The earlier `/hydra-*` names still work and print a one-time deprecation warning | Command | Earlier name | Description | | ------------------------------ | ----------------- | ---------------------------------------------- | -| `/hydradb-ingest ` | `/hydra-remember` | Save a note to HydraDB as a text item | +| `/hydradb-ingest ` | `/hydra-remember` | Save a note to HydraDB as a text context | | `/hydradb-query ` | `/hydra-recall` | Query HydraDB and list the results with scores | | `/hydradb-list` | `/hydra-list` | List everything stored in the collection | -| `/hydradb-delete ` | `/hydra-delete` | Delete one stored item by its ID | -| `/hydradb-inspect ` | `/hydra-get` | Show an item's content (first 2,000 characters) | +| `/hydradb-delete ` | `/hydra-delete` | Delete one stored context by its ID | +| `/hydradb-inspect ` | `/hydra-get` | Show a context's content (first 2,000 characters) | | `/hydra-onboard` | - | Show current configuration status | --- @@ -178,11 +178,11 @@ The earlier `hydra_*` names still work and print a one-time deprecation warning. | Tool | Earlier name | Description | | ------------------ | ---------------------- | -------------------------------------------------------------- | -| `hydradb_ingest` | `hydra_store` | Save the recent conversation (up to the last 10 turns) as a conversation item, or the given text when there is no conversation | +| `hydradb_ingest` | `hydra_store` | Save the recent conversation (up to the last 10 turns) as a conversation context, or the given text when there is no conversation | | `hydradb_query` | `hydra_search` | Query HydraDB; returns the server-built `llm_prompt` | | `hydradb_list` | `hydra_list_memories` | List everything stored (IDs and summaries) | -| `hydradb_inspect` | `hydra_get_content` | Fetch an item's content by its ID (`source_id`), up to the first 3,000 characters | -| `hydradb_delete` | `hydra_delete_memory` | Delete one stored item by its ID (`memory_id`); use only on explicit request | +| `hydradb_inspect` | `hydra_get_content` | Fetch a context's content by its ID (`source_id`), up to the first 3,000 characters | +| `hydradb_delete` | `hydra_delete_memory` | Delete one stored context by its ID (`memory_id`); use only on explicit request | --- @@ -194,8 +194,8 @@ openclaw hydradb onboard --advanced # Advanced onboarding wizard openclaw hydradb query # Query HydraDB openclaw hydradb ingest # Save a note openclaw hydradb list # List everything stored -openclaw hydradb delete # Delete an item -openclaw hydradb inspect # Fetch an item's content +openclaw hydradb delete # Delete a context +openclaw hydradb inspect # Fetch a context's content openclaw hydradb status # Show plugin configuration ``` From 876008986c731a77499f51775c2f4037b7a5dbd8 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 00:49:44 +0530 Subject: [PATCH 10/17] docs: API reference renders the linted docs view of the spec api-reference/v2/openapi.json is now hydradb-application's docs/openapi.docs.json (app PR #1669): /query answers with the typed four-key body, deprecated fields carry the badge and one line, internal fields are hidden, and no page shows Option tabs. temporal_intent is not public, so its rows go; the inspect snippets no longer name a variable item. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 1 - api-reference/v2/endpoint/fetch-content.mdx | 4 +- api-reference/v2/openapi.json | 9706 +++++-------------- api-reference/v2/sdks.mdx | 4 +- essentials/v2/query.mdx | 1 - 5 files changed, 2549 insertions(+), 7167 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index d1ebd08d..7db92ac9 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -878,7 +878,6 @@ Rules: | `follow_forceful_relations` | boolean | Default `true`: pull declared forceful relations into `forceful_relations[]` (`thinking` mode only). | | `temporal_reasoning` | boolean | Default `true`. Resolve time-based questions (current, as of, ranges); matched facts come back in `chunks[].temporal`. Never changes which chunks are returned. | | `temporal_now` | ISO 8601 string | The time to treat as now, for example when replaying a past conversation. | -| `temporal_intent` | object | Override the temporal intent HydraDB would infer from the query. | ### Recommended configurations diff --git a/api-reference/v2/endpoint/fetch-content.mdx b/api-reference/v2/endpoint/fetch-content.mdx index 4d4f42f5..45b37c05 100644 --- a/api-reference/v2/endpoint/fetch-content.mdx +++ b/api-reference/v2/endpoint/fetch-content.mdx @@ -11,7 +11,7 @@ Specify the `id` of the context you want to retrieve. The response carries the s ```python Python SDK -item = client.context.inspect( +inspected = client.context.inspect( id="policy_main", database="acme_corp", mode="both", @@ -20,7 +20,7 @@ item = client.context.inspect( ``` ```typescript TypeScript SDK -const item = await client.context.inspect({ +const inspected = await client.context.inspect({ id: "policy_main", database: "acme_corp", mode: "both", diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index 055a7d33..77e0c037 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -4,56 +4,35 @@ "connectors.Resource": { "properties": { "acl": { - "description": "ACL is the customer-declared access-control list stamped onto every\nobject synced from this resource (PRO-1684; see internal/domain/acl).\nStored in caller-supplied form and normalized at transform time. nil\nmeans no ACL, documents stay unrestricted. Provider-derived ACLs\n(Phase 2) take precedence over this when the provider supports them.", + "description": "Access rule applied to every object synced from this resource: the listed principals (emails or prefixed principals) can read them. Absent means unrestricted. Permissions captured from the provider take precedence where the provider supports them.", "items": { "type": "string" }, "type": "array", "uniqueItems": false }, - "acl_fingerprint": { - "description": "ACLFingerprint is the stable identity of the ACL last APPLIED to this\nresource's already-indexed documents (PRO-1684). The sync compares the\nfreshly-resolved provider ACL against it: equal means nothing to do,\ndifferent means fan the new ACL out to existing documents. Empty means\nnothing has been applied yet (first capture-enabled sync).", - "type": "string" - }, "acl_warning": { - "description": "ACLWarning explains, in the provider's own words, why this resource's\npermissions could not be captured. Capture fails OPEN, so the resource\nis readable by everyone while this is set; without surfacing it, that\nwidening would be invisible to the person who turned RBAC on. Cleared\nautomatically by the next successful capture.", + "description": "Set when the provider's permissions for this resource could not be captured. The provider's own explanation. While set, objects from this resource are readable by everyone; it clears automatically after the next successful capture.", "type": "string" }, "acl_warning_at": { - "description": "ACLWarningAt is when this warning last CHANGED (RFC3339). An unchanged\nwarning is not rewritten each cycle, so it reads as \"open since\".", + "description": "When `acl_warning` last changed (RFC 3339). An unchanged warning keeps its original time, so this reads as \"open since\".", "type": "string" }, "additional_metadata": { "additionalProperties": {}, - "description": "AdditionalMetadata is merged into the additional_metadata (document\nmetadata) layer of every object synced from this resource. User-supplied\nkeys are shallow-merged as the base; provider-generated fields are\napplied on top and always win on conflict.", + "description": "Key-value pairs merged into the custom attributes (`additional_metadata`) of every object synced from this resource. Provider-generated fields win on conflict.", "example": { "author": "ada", "doc_version": 3 }, "type": "object" }, - "backfill_chunk_interval_seconds": { - "description": "BackfillChunkIntervalSeconds is the pacing interval persisted at configure\ntime so the scheduler can thread it into each chunk's workflow input.", - "example": 86400, - "type": "integer" - }, - "backfill_floor": { - "description": "BackfillFloor is the fixed oldest boundary the historical crawl is working\ntowards, stamped once at configure time as now-lookback_days.\n\nIt exists because the floor used to be recomputed per chunk from the\nworkflow's own clock, which made it a *moving* target: every hour the\ncrawl was delayed, the boundary advanced an hour with it. A connector\npaused mid-backfill (PRO-1762) makes that trivially reachable — pause for\nlonger than the crawl has left and it resumes, finds backfill_oldest\nalready at or past the recomputed floor, declares itself complete and\nclears the marker. The remaining history is never fetched and nothing\nreports it missing. Anchoring the boundary is what makes \"backfill 30\ndays\" mean 30 days from when it was asked for, however long the crawl\ntakes.\n\nEmpty on rows configured before this field existed; the workflow falls\nback to the old now-relative computation for those, so their behaviour is\nunchanged rather than silently altered by a deploy.", - "type": "string" - }, - "backfill_next_chunk_at": { - "description": "BackfillNextChunkAt is the RFC3339 time the next chunk becomes due. The\nbackfill workflow processes one chunk then sets this to now+interval and\nexits; the connector scheduler starts the next chunk once it passes.", - "type": "string" - }, "backfill_oldest": { - "description": "BackfillOldest is an RFC3339 timestamp marking the oldest boundary remaining\nfor async historical backfill. Empty means backfill is complete or not needed.", + "description": "Oldest point (RFC 3339) the background fetch of older history has reached so far; it moves back as chunks complete. Empty when that fetch is finished or was not needed.", "example": "2026-06-01T00:00:00Z", "type": "string" }, - "backfill_status": { - "description": "BackfillStatus gates the sparse ResourcesByBackfillNextChunkAt GSI: it is\nset to BackfillStatusActive while a historical backfill is in progress and\nremoved when it completes, so only actively-backfilling resources appear in\nthe scheduler's due query. Pacing between chunks is driven by that scheduler\n(see BackfillNextChunkAt), not by an in-workflow sleep.", - "type": "string" - }, "collection_override": { "description": "Routes this resource's synced objects into a specific collection, overriding the connector's. Canonical name; mirrors the deprecated `sub_tenant_id_override` alias.", "type": "string" @@ -64,11 +43,11 @@ "type": "string" }, "custom_instructions": { - "description": "CustomInstructions is optional free-text ingestion guidance scoped to\nthis resource. When set it replaces the connector-level\ncustom_instructions for documents synced from this resource; empty means\nthe resource inherits the connector's value. Max 4000 characters;\nchanges apply from the next sync cycle.", + "description": "Instructions that steer how documents synced from this resource are ingested and indexed. When set, replaces the connector's `custom_instructions` for this resource; empty inherits the connector's value. Up to 4000 characters; changes apply from the next sync.", "type": "string" }, "database_override": { - "description": "DatabaseOverride/CollectionOverride are the canonical v2 names for the\ndeprecated tenant_id_override/sub_tenant_id_override wire fields. Empty\nmeans the resource inherits the connector's database/collection, exactly\nas the deprecated fields do. Not persisted (dynamodbav:\"-\"): mirrored from\nthe tenant_id_override/sub_tenant_id_override values at construction time.", + "description": "Database this resource's synced objects are routed to. Empty means the connector's own database.", "type": "string" }, "display_name": { @@ -86,7 +65,7 @@ }, "metadata": { "additionalProperties": {}, - "description": "Metadata is merged into the tenant metadata layer of every object synced\nfrom this resource. User-supplied keys are shallow-merged as the base;\nsystem defaults (connector_id, provider) are applied on top so they\nalways win on conflict — user keys extend the map but cannot override\nsystem-set fields.", + "description": "Key-value pairs merged into the attributes (`metadata`) of every object synced from this resource. The system fields `connector_id` and `provider` always win on conflict.", "example": { "department": "finance", "priority": 7 @@ -94,15 +73,11 @@ "type": "object" }, "page_acl_warning": { - "description": "PageACLWarning is the same signal for SOURCE-level failures inside this\nresource: individual pages whose own restrictions could not be resolved\nand were therefore opened (Confluence, PRO-1684).\n\nA SEPARATE field from ACLWarning on purpose. The two are written by\ndifferent steps at different points in a sync, and ACLWarning is CLEARED\nwhenever resource capture succeeds. Sharing one field would let a healthy\nspace wipe a live page warning every cycle, leaving a window in which the\ndashboard reports no problems while pages are still open — a false\nall-clear on an access-control surface, which is worse than no surface.", + "description": "Set when restrictions on individual pages inside this resource (for example Confluence pages) could not be resolved, so those pages are readable by everyone. Reported separately from `acl_warning` and cleared after a full sync in which no page fails.", "type": "string" }, "page_acl_warning_at": { - "description": "PageACLWarningAt is when PageACLWarning last CHANGED (RFC3339).", - "type": "string" - }, - "page_acl_warning_run": { - "description": "PageACLWarningRun is the drain run that last observed a page failing open\nhere. It is what makes the warning self-clearing: the drain settles each\nresource at the END of a cycle, and a stored run that is not the current\none means that whole cycle passed with nothing failing, so the warning is\nwithdrawn. Durable on purpose — the alternative was remembering it in the\nworker, which a restart loses and which has no moment that means \"all\npages have now been judged\".", + "description": "When `page_acl_warning` last changed (RFC 3339).", "type": "string" }, "provider_cursor": { @@ -135,26 +110,26 @@ }, "sub_tenant_id_override": { "deprecated": true, - "description": "Overrides the connector-level collection for objects synced from this resource.", + "description": "Deprecated: use `collection_override`.", "type": "string", "x-deprecated": "true" }, "sync_blocked": { - "description": "SyncBlocked marks a resource the provider will go on refusing — a table\nthat was dropped, a channel this credential was never invited to.\n\nDeliberately not a Status value. Status gates ListConnectorResources,\nwhich is what GET /connectors/{id}/status reads, so expressing this as a\nstatus would hide the resource from the one endpoint that explains why it\nstopped. The resource stays active and visible; this only takes it out of\nwhat gets synced.", + "description": "`true` when syncing of this resource has stopped because the provider keeps refusing it, for example a deleted table or a channel the credential cannot access. The resource stays listed; `sync_blocked_reason` says why.", "example": true, "type": "boolean" }, "sync_blocked_at": { - "description": "SyncBlockedAt is when the resource was stopped (RFC3339).", + "description": "When syncing of this resource was stopped (RFC 3339). Present only while `sync_blocked` is `true`.", "type": "string" }, "sync_blocked_reason": { - "description": "SyncBlockedReason is the provider's own explanation, carried forward from\nthe health that triggered the block so it survives the next sync\noverwriting that health.", + "description": "The provider's explanation for why this resource is blocked. Present only while `sync_blocked` is `true`.", "type": "string" }, "tenant_id_override": { "deprecated": true, - "description": "Overrides the connector-level database for objects synced from this resource. Deprecated.", + "description": "Deprecated: use `database_override`.", "type": "string", "x-deprecated": "true" } @@ -179,12 +154,12 @@ "type": "integer" }, "page_size": { - "description": "Number of items per page.", + "description": "Number of results per page.", "example": 50, "type": "integer" }, "total": { - "description": "Total number of items across all pages.", + "description": "Total number of results across all pages.", "example": 128, "type": "integer" }, @@ -199,12 +174,12 @@ "feedback.GroundTruth": { "properties": { "answer": { - "description": "Answer is the response the caller expected — the text a correct system\nwould have produced from the retrieved context.", + "description": "The answer you expected: the text a correct system would have produced from the retrieved context. At most 8,000 characters.", "maxLength": 8000, "type": "string" }, "source_ids": { - "description": "SourceIDs are the ingested source IDs that actually contain the answer,\nas returned in query results and accepted by /context endpoints.", + "description": "The `context_id`s that actually contain the answer, as returned by /query. At most 100.", "example": [ "HydraDoc1234", "HydraDoc4567" @@ -245,88 +220,6 @@ ] }, "feedback.SubmitRequest": { - "allOf": [ - { - "anyOf": [ - { - "patternProperties": { - "^feedback$": { - "minLength": 1, - "pattern": "\\S" - } - }, - "required": [ - "feedback" - ] - }, - { - "patternProperties": { - "^ground_truth$": { - "anyOf": [ - { - "properties": { - "answer": { - "minLength": 1, - "pattern": "\\S" - } - }, - "required": [ - "answer" - ] - }, - { - "properties": { - "source_ids": { - "contains": { - "minLength": 1, - "pattern": "\\S" - } - } - }, - "required": [ - "source_ids" - ] - } - ] - } - }, - "required": [ - "ground_truth" - ] - } - ] - } - ], - "dependentSchemas": { - "collection": { - "anyOf": [ - { - "required": [ - "database" - ] - }, - { - "required": [ - "tenant_id" - ] - } - ] - }, - "sub_tenant_id": { - "anyOf": [ - { - "required": [ - "database" - ] - }, - { - "required": [ - "tenant_id" - ] - } - ] - } - }, "properties": { "collection": { "description": "Optional collection scope for this feedback. A collection is scoped to a database, so `database` must be sent alongside it; sending `collection` on its own is rejected.", @@ -346,8 +239,12 @@ "type": "string" }, "ground_truth": { - "$ref": "#/components/schemas/feedback.GroundTruth", - "description": "What you already know the right answer to be, when you know it. Supply an expected `answer`, the `source_ids` that contain it, or both — at least one is required if the field is present. Machine-checkable, so it is a stronger signal than a comment: submit it alone and `feedback` becomes optional.", + "allOf": [ + { + "$ref": "#/components/schemas/feedback.GroundTruth" + } + ], + "description": "What you know the right answer to be. Send an expected `answer`, the `source_ids` that contain it, or both; at least one is required when the field is present. It is a stronger signal than a comment: send it alone and `feedback` becomes optional.", "example": { "source_ids": [ "HydraDoc1234", @@ -376,7 +273,7 @@ "description": "Optional overall judgement: `positive`, `negative`, or `neutral`. Omit to send a comment with no rating." }, "request_id": { - "description": "The `request_id` from `response.meta` of the query this feedback is about. Required — it is what links the feedback to the query that ran.", + "description": "The `request_id` from `meta` of the query this feedback is about. Required: it links the feedback to the query that ran.", "example": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", "format": "uuid", "type": "string" @@ -441,31 +338,31 @@ "fetch.V2SourceFetchResponse": { "properties": { "content": { - "description": "Extracted text content of the source document.", + "description": "The stored content when it is UTF-8 text, else `null`. A conversation is stored as JSON. `null` in `url` mode.", "example": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", "type": "string" }, "content_base64": { - "description": "Base64-encoded binary content, for binary file types.", + "description": "The stored content, base64-encoded, when it is not UTF-8 text. `null` in `url` mode.", "type": "string" }, "content_type": { - "description": "MIME type of the source (e.g. `application/pdf`, `text/plain`).", + "description": "MIME type of the stored content, for example `text/plain`.", "example": "application/pdf", "type": "string" }, "error": { - "description": "Error message, empty string on success.", + "description": "Error message, or `null` on success.", "example": "", "type": "string" }, "id": { - "description": "Unique identifier for this resource.", + "description": "The context's ID.", "example": "HydraDoc1234", "type": "string" }, "inferred_content": { - "description": "LLM-generated summary of the source content.", + "description": "The enrichment written for the context, or `null` when there is none yet or `enrich` was off. `null` in `url` mode.", "example": "Summary: Q4 revenue rose 23% QoQ, driven by enterprise expansion.", "type": "string" }, @@ -475,18 +372,18 @@ "type": "string" }, "presigned_url": { - "description": "Time-limited download URL for the original file.", + "description": "Time-limited URL that downloads the stored content, valid for `expiry_seconds`. `null` in `content` mode.", "example": "https://storage.hydradb.com/sources/HydraDoc1234?sig=...", "type": "string" }, "size_bytes": { - "description": "File size in bytes.", + "description": "Size of the stored content in bytes.", "example": 20480, "type": "integer" }, "success": { "deprecated": true, - "description": "Deprecated for API clients: to decide whether the request succeeded,\ncheck the HTTP status code — 2xx is success — or equivalently the\nenvelope's top-level `success`. This nested copy always carries the same\nvalue as that flag and never carries independent information. Still\nemitted unchanged for existing clients (PRO-1208).", + "description": "Deprecated: check the HTTP status instead. Always equals the envelope's top-level `success`.", "example": true, "type": "boolean", "x-deprecated": "true" @@ -495,7 +392,7 @@ "type": "object" }, "github_com_hydradb_hydradb-application_internal_platform_storagelayout.Layout": { - "description": "StorageLayout is the physical storage layout the database is created with,\nfrom the request's `type` field. \"split\" is the two-collection layout every\ndatabase uses, and the default. Fixed at creation and IMMUTABLE thereafter:\nthe layout decides how every entity id is hashed, so a database that changed\nits mind would orphan everything already stored.", + "description": "The database's storage layout.", "enum": [ "split" ], @@ -506,13 +403,8 @@ }, "github_com_hydradb_hydradb-application_internal_service.MetadataEditResult": { "properties": { - "acl_drift_recorded": { - "description": "ACLDriftRecorded reports that a failed ACL mirror was durably recorded\nfor reconciliation. Always true when vector_acl_synced is true. False\nbeside acl_updated=true and vector_acl_synced=false is the one state\nthe operator must act on (the error log names the document).", - "example": true, - "type": "boolean" - }, "acl_updated": { - "description": "ACLUpdated reports that this edit replaced the source's ACL (PRO-1684).", + "description": "`true` when the request replaced the context's `acl`. Omitted otherwise.", "example": true, "type": "boolean" }, @@ -525,27 +417,27 @@ "uniqueItems": false }, "chunk_rows_matched": { - "description": "Number of MongoDB chunk rows matched by the source update.", + "description": "Number of stored chunks of the context matched by the update.", "example": 1, "type": "integer" }, "chunk_rows_modified": { - "description": "Number of MongoDB chunk rows modified by the source update.", + "description": "Number of stored chunks changed by the update.", "example": 1, "type": "integer" }, "collection": { - "description": "Collection that contained the source. Canonical name; mirrors the deprecated `sub_tenant_id` alias.", + "description": "Collection that holds the context.", "example": "team_docs", "type": "string" }, "database": { - "description": "Owning database. Canonical name; mirrors the deprecated `tenant_id` alias.", + "description": "Database named in the request.", "example": "acme_corp", "type": "string" }, "database_metadata_keys": { - "description": "Database metadata keys included in the update request. Canonical name; `tenant_metadata_keys` is a deprecated alias.", + "description": "Keys of `database_metadata` included in the request.", "example": [ "department", "priority" @@ -557,52 +449,52 @@ "uniqueItems": false }, "id": { - "description": "Unique identifier for this resource.", + "description": "`context_id` of the updated context.", "example": "HydraDoc1234", "type": "string" }, "milvus_rows_synced": { "deprecated": true, - "description": "deprecated: use vector_rows_synced", + "description": "Deprecated: use `vector_rows_synced`. Same value.", "example": 1, "type": "integer", "x-deprecated": "true" }, "milvus_sync_required": { "deprecated": true, - "description": "Deprecated: use vector_sync_required / vector_synced / vector_rows_synced.\nRetained as additive aliases for existing clients; carry the same values.", + "description": "Deprecated: use `vector_sync_required`. Same value.", "example": true, "type": "boolean", "x-deprecated": "true" }, "milvus_synced": { "deprecated": true, - "description": "deprecated: use vector_synced", + "description": "Deprecated: use `vector_synced`. Same value.", "example": true, "type": "boolean", "x-deprecated": "true" }, "partial_commit": { - "description": "PartialCommit reports that the edit committed in at least one database\nof a shared deployment but a later write in another failed; the\nidempotent retry converges the database that fell behind.", + "description": "Present when only part of the edit was saved; the text describes the failure. Retry the same edit; it is idempotent.", "type": "string" }, "sub_tenant_id": { "deprecated": true, - "description": "deprecated: use collection", + "description": "Deprecated: use `collection`. Same value.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`. Same value.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" }, "tenant_metadata_keys": { "deprecated": true, - "description": "deprecated: use database_metadata_keys", + "description": "Deprecated: use `database_metadata_keys`. Same value.", "items": { "type": "string" }, @@ -611,31 +503,31 @@ "x-deprecated": "true" }, "updated": { - "description": "Whether the source metadata was updated.", + "description": "`true` when the context was updated.", "example": true, "type": "boolean" }, "vector_acl_synced": { - "description": "VectorACLSynced reports that the ACL edit also reached the vector rows'\npushdown columns (PRO-1740). False with ACLUpdated true means the\ndocument's own-ACL projection is stale until its next re-index: still\nenforced correctly from Mongo, but invisible to the pushdown lane for\nany principal the edit ADDED. Always false for collections created\nbefore PRO-1740, which carry no pushdown columns.", + "description": "`true` when the new `acl` also reached the search index. If `acl_updated` is `true` and this is absent, principals the edit added may not find the context in search until it is re-indexed; the list is still enforced on every result.", "example": true, "type": "boolean" }, "vector_rows_synced": { - "description": "Number of chunk rows synced to the vector store when sync was required.", + "description": "Number of chunks updated in the search index when `vector_sync_required` is `true`. Absent otherwise.", "example": 1, "type": "integer" }, "vector_sync_error": { - "description": "VectorSyncError explains a vector_synced=false when a sync was\nrequired: the authority (Mongo) committed, the vector metadata did\nnot follow; the idempotent retry converges it.", + "description": "Present when a required search index update failed. The edit itself was saved; retry the same edit to converge it.", "type": "string" }, "vector_sync_required": { - "description": "Vendor-neutral vector-sync signal (PRO-1185): the canonical field must not\nname the vector store. The milvus_* fields below are deprecated aliases kept\nfor backward compatibility (additive change, not a rename) and carry the same\nvalues; they are slated for removal in a future major version.", + "description": "`true` when at least one changed attribute has dense or sparse embedding enabled, so the search index must be updated too.", "example": true, "type": "boolean" }, "vector_synced": { - "description": "Whether the vector store metadata sync completed. Present when sync was required.", + "description": "`true` when the required search index update completed. Absent when no update was needed or it failed; see `vector_sync_error`.", "example": true, "type": "boolean" } @@ -650,11 +542,11 @@ "type": "string" }, "hydration": { - "description": "Hydration is set on Source nodes in AuxiliaryRelations only, and omitted\neverywhere else. RELATES_TO MERGEs its target by source_id, so a target\nthat has not been ingested yet still exists as a node — callers must be\nable to tell a real document from a forward reference to one.\n\n\tresolved — ingested; source_id and app_provider both present\n\tstub — MERGE-created target; source_id present, no app_provider\n\tplaceholder — source_id IS NULL, keyed by app_external_id, awaiting\n\t builder.py's reconciliation pass", + "description": "Set only on context nodes in `auxiliary_relations`, omitted elsewhere. Whether the context is ingested: `resolved` (ingested), `stub` (a relation points at it but it is not ingested yet) or `placeholder` (known only by its provider id, not yet matched to an ingested context).", "type": "string" }, "identifier": { - "description": "NO omitempty — serialize as null", + "description": "External identifier of the entity when one is known, for example a person's handle in the connected app. `null` otherwise.", "example": "Acme Corp", "type": "string" }, @@ -669,7 +561,7 @@ "type": "string" }, "provider": { - "description": "Provider is the source app the entity's evidence chunk came from (e.g.\n\"slack\", \"google\", \"intercom\"), read from the owning Source node's\napp_provider. Empty string when the evidence has no app source (plain\ndocument / web ingest). Consumed by the dashboard to render a connector\nlogo inside the graph node.", + "description": "Connector the entity's evidence came from, for example `slack` or `google`. Empty string when the evidence did not come from a connector.", "example": "slack", "type": "string" }, @@ -684,7 +576,7 @@ "graph.GraphRelationsResponse": { "properties": { "auxiliary_relations": { - "description": "AuxiliaryRelations carries the structural graph around the entity\nrelations: Entity-\u003eSource presence, Source-\u003eComment/Attachment,\nActor-\u003eSource/Comment, and Source-\u003eSource links. Same item shape as\nRelations, so a caller wanting one graph concatenates the two.\n\nDeliberately a SEPARATE array rather than merged into Relations:\ncapPreservingTies counts triplets against the caller's limit, and\ncomputeNextCursor keys on relation timestamps. Auxiliary edges carry\ncreated_at — a different clock — so merging them would both shrink the\nentity relations returned for a given limit and corrupt the cursor.", + "description": "The structural graph around the entity relations: where entities appear, comments and attachments, who authored what, and links between contexts. Same entry shape as `relations`, so concatenate the two for one graph. Not counted against `limit` and does not move the cursor.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -729,12 +621,12 @@ "uniqueItems": false }, "auxiliary_truncated": { - "description": "AuxiliaryTruncated reports that an aggregate row ceiling clipped the\nauxiliary graph. Distinct from the per-triplet Truncated flag, which only\ncovers a single node exceeding its fan-out cap: a wide page can blow the\naggregate ceiling with every individual node still under its own cap, and\nwithout this the caller would receive a subset presented as complete.\n\nIndependent of IsTruncated, which describes Relations pagination only.", + "description": "`true` when a size limit cut `auxiliary_relations` short. Independent of `is_truncated`, which covers `relations` only.", "example": true, "type": "boolean" }, "is_truncated": { - "description": "Whether the response was truncated due to the result limit.", + "description": "`true` when more relations exist than this page returned. Fetch the rest with `next_cursor`.", "example": false, "type": "boolean" }, @@ -744,12 +636,12 @@ "type": "string" }, "next_cursor": { - "description": "NO omitempty", + "description": "Opaque cursor for the next page; pass it back as `cursor` exactly as returned. `null` when there are no more relations.", "example": 0.5, "type": "number" }, "relations": { - "description": "Array of triplet groups with evidence for each relationship.", + "description": "Entity relations, grouped per entity pair. This is the list `limit` and `cursor` page through.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -795,7 +687,7 @@ }, "success": { "deprecated": true, - "description": "Deprecated for API clients: to decide whether the request succeeded,\ncheck the HTTP status code — 2xx is success — or equivalently the\nenvelope's top-level `success`. This nested copy always carries the same\nvalue and never carries independent information. Still emitted unchanged\nfor existing clients (PRO-1208).", + "description": "Deprecated: use the HTTP status code or the envelope's top-level `success`. Always carries the same value.", "example": true, "type": "boolean", "x-deprecated": "true" @@ -811,7 +703,7 @@ "type": "string" }, "chunk_id": { - "description": "NO omitempty", + "description": "Chunk the relation was extracted from. `null` on relations that were not extracted from text.", "example": "HydraEmbeddings123_0", "type": "string" }, @@ -827,7 +719,7 @@ }, "properties": { "additionalProperties": {}, - "description": "Properties are the caller's own properties on a forceful_relation edge,\nexactly as declared at ingest (`forceful_relations.properties`). Flat\nscalars. Omitted on every other edge and on a forceful relation that\ndeclared none.", + "description": "Your own properties on a relation declared in `forceful_relations` at ingest, exactly as sent. Flat scalar values. Omitted on every other relation and on a forceful relation that declared none.", "type": "object" }, "raw_predicate": { @@ -841,27 +733,27 @@ "type": "string" }, "source_entity_id": { - "description": "NO omitempty", + "description": "`entity_id` of the relation's source entity, or `null` when not recorded.", "example": "entity_1a2b", "type": "string" }, "synthesized": { - "description": "Synthesized marks a triplet with no stored edge behind it. Only\n`present_in` sets it: that edge is derived by collapsing\nEntity-PRESENT_IN-\u003eChunk-HAS_CHUNK-\u003eSource, so its RelationshipID is a\ndeterministic synthetic id rather than a graph relationship id. Omitted\n(false) on every stored edge.", + "description": "`true` on a relation derived by the API rather than stored: `present_in`, and the `same_thread` and `child_of` relations in a subgraph. Its `relationship_id` is generated. Omitted otherwise.", "example": true, "type": "boolean" }, "target_entity_id": { - "description": "NO omitempty", + "description": "`entity_id` of the relation's target entity, or `null` when not recorded.", "example": "entity_3c4d", "type": "string" }, "temporal_details": { - "description": "NO omitempty", + "description": "Time information extracted with the relation, as free text (for example `2026-02`). `null` when there is none.", "example": "since 2024", "type": "string" }, "timestamp": { - "description": "RFC3339 timestamp associated with this item.", + "description": "When the relation was recorded (RFC 3339).", "example": "2026-07-02T10:00:00Z", "type": "string" } @@ -871,7 +763,7 @@ "graph.SourceSubgraphResponse": { "properties": { "auxiliary_relations": { - "description": "AuxiliaryRelations carries the structural graph around the member\nsources: Entity-\u003eSource presence, Source-\u003eComment/Attachment and\nActor-\u003eSource/Comment links. Same item shape as Relations, matching\nGraphRelationsResponse so the dashboard renderer works unchanged.", + "description": "The structural graph around the members: which entities appear in them, their comments and attachments, and who authored them. Same entry shape as `relations`.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -916,17 +808,17 @@ "uniqueItems": false }, "auxiliary_truncated": { - "description": "AuxiliaryTruncated reports that a fetch ceiling clipped the auxiliary\ngraph, same contract as GraphRelationsResponse.AuxiliaryTruncated.", + "description": "`true` when a size limit cut `auxiliary_relations` short.", "example": true, "type": "boolean" }, "is_truncated": { - "description": "IsTruncated reports that the traversal stopped before exhausting the\nconnected component: the source budget or an edge/expansion fetch cap\nwas hit, or the depth limit left an unexpanded frontier.", + "description": "`true` when the traversal stopped before reaching every connected context, because `max_sources`, `depth` or a size limit cut it off.", "example": false, "type": "boolean" }, "max_depth_reached": { - "description": "MaxDepthReached is the deepest BFS level that admitted a member.", + "description": "Largest `depth` of any member. `0` when only the start context, or nothing, was found.", "example": 1, "type": "integer" }, @@ -936,7 +828,7 @@ "type": "string" }, "relations": { - "description": "Relations holds the Source-\u003eSource triplets: every RELATES_TO edge whose\nendpoints are both members, plus synthesized same_thread / child_of\nprovenance edges for members reached through a node property rather than\na stored edge (those carry Synthesized on their evidence).", + "description": "Relations among the members: declared `relates_to` links whose ends are both members, plus `same_thread` and `child_of` relations for members reached through a shared thread or a hierarchy (marked `synthesized`).", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -981,10 +873,11 @@ "uniqueItems": false }, "seed_source_id": { + "description": "The `id` the traversal started from, as requested.", "type": "string" }, "sources": { - "description": "Sources is every member of the subgraph in BFS discovery order, seed\nfirst.", + "description": "Every member of the subgraph in breadth-first discovery order, the start context first at `depth` `0`. Empty for an unknown `id`.", "example": [ { "app_external_id": "C0123456789", @@ -1022,31 +915,34 @@ "type": "string" }, "app_provider": { - "description": "Provider name for app-sourced items (e.g. `slack`, `github`).", + "description": "Provider a connector-synced context came from, for example `slack` or `github`. Absent on ingested contexts.", "example": "slack", "type": "string" }, "depth": { - "description": "Depth is the BFS distance from the seed (0 for the seed itself).", + "description": "Hops from the start context. `0` for the start context itself.", "example": 1, "type": "integer" }, "discovered_relation": { + "description": "How this member was reached: `same_thread`, `parent`, `child`, or the relation type of a declared `relates_to` link (for example `reply_to`). Omitted on the start context.", "type": "string" }, "discovered_via": { - "description": "DiscoveredVia and DiscoveredRelation record the traversal provenance:\nwhich already-admitted source this member was first reached from, and\nthrough which mechanism — a RELATES_TO relation_type (reply_to,\nchild_of, ...), same_thread, parent or child. Empty on the seed.", + "description": "`source_id` of the member this one was first reached from. Follow it back to rebuild the traversal tree. Omitted on the start context.", "type": "string" }, "hydration": { - "description": "Hydration carries the same resolved/stub/placeholder classification\nEntity.Hydration documents: a RELATES_TO target may be a MERGE-created\nforward reference to a document that has not been ingested yet.", + "description": "Whether the context is ingested: `resolved` (ingested), `stub` (a relation points at it but it is not ingested yet) or `placeholder` (known only by its provider id, not yet matched to an ingested context).", "type": "string" }, "source_id": { + "description": "`context_id` of the member. Pass it to Inspect Context for the full content, or back to this endpoint to re-centre the subgraph on it.", "example": "HydraDoc1234", "type": "string" }, "thread_id": { + "description": "Thread the context belongs to in the connected app, for example a Slack thread. Omitted when it has none.", "type": "string" }, "title": { @@ -1110,7 +1006,7 @@ } }, "truncated": { - "description": "Truncated is set on auxiliary triplets whose fan-out hit a per-node cap,\nso a caller can tell \"this source has no more comments\" from \"we stopped\ncounting\". Omitted (false) on entity relations, which are bounded by the\nrequest's own limit/cursor instead.", + "description": "`true` on an `auxiliary_relations` entry whose list was cut at a per-node limit, for example a context with more comments than were returned. Omitted otherwise.", "example": true, "type": "boolean" } @@ -1205,7 +1101,6 @@ "data": { "$ref": "#/components/schemas/github_com_hydradb_hydradb-application_internal_service.MetadataEditResult", "example": { - "acl_drift_recorded": true, "acl_updated": true, "chunk_rows_matched": 1, "chunk_rows_modified": 1, @@ -1494,43 +1389,6 @@ }, "type": "object" }, - "handler.Envelope-handler_supabaseWebhookAck": { - "properties": { - "data": { - "$ref": "#/components/schemas/handler.supabaseWebhookAck", - "example": { - "id": "HydraDoc1234", - "status": "completed" - } - }, - "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } - }, - "meta": { - "$ref": "#/components/schemas/handler.responseMeta", - "example": { - "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": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, "handler.Envelope-ingestion_V2BatchProcessingStatus": { "properties": { "data": { @@ -1724,40 +1582,48 @@ }, "type": "object" }, - "handler.Envelope-search_ChunkInspectResult": { + "handler.Envelope-search_V2RetrievalResult": { "properties": { "data": { - "$ref": "#/components/schemas/search.ChunkInspectResult", + "$ref": "#/components/schemas/search.QueryResult", + "description": "The response body: ranked chunks, graph paths, forceful relations and a prompt-ready string.", "example": { "chunks": [ { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_id": "HydraDoc1234", - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" + "chunk_id": "refund-policy_0", + "content": "Refunds are processed within 30 days of the request. Finance owns approvals.", + "context_id": "refund-policy", + "enrichment": "Refund window is 30 days; Finance approves refunds.", + "received_at": "2026-07-02T09:14:05Z", + "score": 0.91 } ], - "is_truncated": false, - "message": "Success", - "success": true + "forceful_relations": [], + "graph": [ + { + "origin": "query_path", + "path_summary": "Finance approves Refunds.", + "triplets": [ + { + "relation": { + "chunk_id": "refund-policy_0", + "context": "Finance owns approvals.", + "predicate": "approves", + "relationship_id": "rel_approves_refunds" + }, + "source": { + "entity_id": "ent_finance", + "name": "Finance" + }, + "target": { + "entity_id": "ent_refunds", + "name": "Refunds" + } + } + ] + } + ], + "llm_prompt": "## Results\n\n### [1] Refund policy\nRefunds are processed within 30 days of the request. Finance owns approvals.\n\n## Related facts\n\n- [P1] Finance approves Refunds. [1]\n" } }, "error": { @@ -1788,20 +1654,22 @@ }, "type": "object" }, - "handler.Envelope-search_EntityProfileView": { + "handler.Envelope-sources_MemoryDeleteResponse": { "properties": { "data": { - "$ref": "#/components/schemas/search.EntityProfileView", + "$ref": "#/components/schemas/sources.MemoryDeleteResponse", "example": { - "entity_id": "entity_1a2b", - "entries": [ + "deleted_count": 1, + "message": "Success", + "results": [ { - "confidence": 0.92 + "deleted": true, + "error": "", + "id": "HydraDoc1234" } ], - "name": "general", - "pending_importance": 1, - "version": 1 + "success": true, + "user_memory_deleted": 1 } }, "error": { @@ -1832,446 +1700,19 @@ }, "type": "object" }, - "handler.Envelope-search_V2RetrievalResult": { + "handler.Envelope-tenants_InfraStatusResponseV2": { "properties": { "data": { - "description": "The response body, in the shape the database answers with: the v2 body (chunks, graph_context, sources and related fields), or the four-key body (chunks, graph, forceful_relations, llm_prompt).", + "$ref": "#/components/schemas/tenants.InfraStatusResponseV2", "example": { - "additional_context": "The user is a senior engineer onboarding to the platform.", - "app_search_fusion": { - "stats": { - "app_chunks": 1, - "app_has_exact_ids": true, - "app_lane_empty_text": true, - "consensus": 1, - "exact_candidates": 1, - "exact_promoted": 1, - "limit": 1, - "normal_chunks": 1, - "normal_displaced": 1, - "tail_added": 1, - "tail_candidates": 1 - }, - "stats_by_pass": [ - { - "app_chunks": 1, - "app_has_exact_ids": true, - "app_lane_empty_text": true, - "consensus": 1, - "exact_candidates": 1, - "exact_promoted": 1, - "limit": 1, - "normal_chunks": 1, - "normal_displaced": 1, - "tail_added": 1, - "tail_candidates": 1 - } - ] - }, - "chunks": [ - { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } - ], - "code_search": { - "duration_ms": 0.5, - "repos": [ - { - "duration_ms": 0.5, - "error": "", - "status": "completed", - "truncated": true, - "unsigned": true - } - ], - "status": "completed" - }, - "forceful_relations": { - "declared": [ - { - "chunk": { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } - } - ], - "inferred": [ - { - "chunk": { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } - } - ] - }, - "graph": { - "paths": [ - { - "chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "relevancy_score": 0.87, - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] - } - ] - }, - "graph_context": { - "chunk_id_to_group_ids": { - "HydraEmbeddings123_0": [ - "grp_1234" - ] - }, - "chunk_relations": [ - { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "group_id": "grp_1234", - "relevancy_score": 0.87, - "source_chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] - } - ], - "query_paths": [ - { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "group_id": "grp_1234", - "relevancy_score": 0.87, - "source_chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] - } - ] - }, - "profile_context": { - "entity_id": "entity_1a2b", - "entries": [ - { - "confidence": 0.92 - } - ], - "name": "general", - "version": 1 - }, - "profile_filter": { - "applied": true, - "degraded": true, - "entity_id": "entity_1a2b", - "found": true, - "selected_entries": 1, - "version": 1 - }, - "profiles": [ - { - "entity_id": "entity_1a2b", - "entries": [ - { - "confidence": 0.92 - } - ], - "name": "general", - "version": 1 - } - ], - "source_facts": [ - { - "app_kind": "slack", - "chunk_id": "HydraEmbeddings123_0", - "provider": "slack", - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "synced_at": 1 - } - ], - "source_filter": { - "applied": true, - "degraded": true, - "matched_facts": 1, - "mode": "thinking", - "provider": "slack", - "thread_scope": true, - "truncated": true - }, - "sources": [ - { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "app_external_id": "C0123456789", - "app_kind": "slack", - "app_provider": "slack", - "collection": "team_docs", - "description": "Internal overview of the Project Phoenix rollout.", - "id": "HydraDoc1234", - "metadata": { - "department": "finance", - "priority": 7 - }, - "sub_tenant_id": "sub_tenant_4567", - "timestamp": "2026-07-02T10:00:00Z", - "title": "Project Phoenix Overview", - "type": "knowledge", - "url": "https://docs.hydradb.com/phoenix" - } - ], - "temporal_duration": { - "approximate": true, - "days": 1, - "from": { - "chunk_id": "HydraEmbeddings123_0", - "event_end": 1, - "event_start": 1, - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "status": "completed" - }, - "pairing_confidence": 0.5, - "to": { - "chunk_id": "HydraEmbeddings123_0", - "event_end": 1, - "event_start": 1, - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "status": "completed" - } - }, - "temporal_facts": [ - { - "chunk_id": "HydraEmbeddings123_0", - "event_end": 1, - "event_start": 1, - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "status": "completed" - } - ], - "temporal_filter": { - "applied": true, - "chunk_scope": 1, - "degraded": true, - "matched_facts": 1, - "mode": "thinking", - "promoted": 1, - "truncated": true - } - }, - "oneOf": [ - { - "$ref": "#/components/schemas/search.V2RetrievalResult" - }, - { - "$ref": "#/components/schemas/search.QueryResult" - } - ] - }, - "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } - }, - "meta": { - "$ref": "#/components/schemas/handler.responseMeta", - "example": { - "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": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, - "handler.Envelope-sources_MemoryDeleteResponse": { - "properties": { - "data": { - "$ref": "#/components/schemas/sources.MemoryDeleteResponse", - "example": { - "deleted_count": 1, - "message": "Success", - "results": [ - { - "deleted": true, - "error": "", - "id": "HydraDoc1234" - } - ], - "success": true, - "user_memory_deleted": 1 - } - }, - "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } - }, - "meta": { - "$ref": "#/components/schemas/handler.responseMeta", - "example": { - "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": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, - "handler.Envelope-tenants_InfraStatusResponseV2": { - "properties": { - "data": { - "$ref": "#/components/schemas/tenants.InfraStatusResponseV2", - "example": { - "database": "acme_corp", - "infra": { - "graph_status": true, - "ready_for_ingestion": true, - "scheduler_status": true, - "vectorstore_status": { - "knowledge": true, - "memories": true + "database": "acme_corp", + "infra": { + "graph_status": true, + "ready_for_ingestion": true, + "scheduler_status": true, + "vectorstore_status": { + "knowledge": true, + "memories": true } }, "message": "Success", @@ -2535,93 +1976,6 @@ }, "type": "object" }, - "handler.Envelope-tenants_TenantMetadataSchemaResponse": { - "properties": { - "data": { - "$ref": "#/components/schemas/tenants.TenantMetadataSchemaResponse", - "example": { - "database": "acme_corp", - "fields": [ - { - "data_type": "VARCHAR", - "enable_dense_embedding": true, - "enable_match": true, - "enable_sparse_embedding": false, - "max_length": 256, - "name": "category" - } - ], - "tenant_id": "acme_corp" - } - }, - "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } - }, - "meta": { - "$ref": "#/components/schemas/handler.responseMeta", - "example": { - "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": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, - "handler.Envelope-tenants_TenantRenameResponse": { - "properties": { - "data": { - "$ref": "#/components/schemas/tenants.TenantRenameResponse", - "example": { - "connector_reassignment": "complete", - "database": "acme_corp", - "message": "Success", - "status": "completed", - "tenant_id": "tenant_1234" - } - }, - "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } - }, - "meta": { - "$ref": "#/components/schemas/handler.responseMeta", - "example": { - "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": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, "handler.Envelope-tenants_TenantStatsResponse": { "properties": { "data": { @@ -2800,44 +2154,6 @@ }, "type": "object" }, - "handler.Envelope-webhooks_SigningSecretResponse": { - "properties": { - "data": { - "$ref": "#/components/schemas/webhooks.SigningSecretResponse", - "example": { - "generated": true, - "message": "Success", - "signing_secret": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY" - } - }, - "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } - }, - "meta": { - "$ref": "#/components/schemas/handler.responseMeta", - "example": { - "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": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, "handler.Envelope-webhooks_WebhookDeleteResponse": { "properties": { "data": { @@ -3026,8 +2342,8 @@ }, "success": { "deprecated": true, - "description": "Deprecated for API clients: always false on this path, so it carries no\ninformation. To detect a failure read the HTTP status code; for what\nwent wrong read the envelope's error.code and error.message, and\nmeta.request_id when reporting it. The whole `detail` object is\ndeprecated legacy — tagging the field individually so SDK users see it\non the property, not just the container (PRO-1208).", - "example": true, + "description": "Deprecated: always `false`. Read the HTTP status, then `error.code` and `error.message`.", + "example": false, "type": "boolean", "x-deprecated": "true" } @@ -3079,7 +2395,7 @@ }, "success": { "description": "Whether the request succeeded.", - "example": true, + "example": false, "type": "boolean" } }, @@ -3088,9 +2404,11 @@ "handler.ErrorMeta": { "properties": { "api_version": { + "description": "Version of the API that served the request, for example `2.0.1`.", "type": "string" }, "latency_ms": { + "description": "Server-side processing time in milliseconds.", "example": 12.3, "type": "number" }, @@ -3104,7 +2422,9 @@ }, "handler.ErrorResponse": { "properties": { - "data": {}, + "data": { + "description": "Always `null` on this error response." + }, "detail": { "$ref": "#/components/schemas/handler.ErrorDetail", "description": "Structured error detail with code, message, and deprecation hints.", @@ -3133,7 +2453,7 @@ }, "success": { "description": "Whether the request succeeded.", - "example": true, + "example": false, "type": "boolean" } }, @@ -3157,17 +2477,21 @@ "handler.catalogConnector": { "properties": { "category": { + "description": "Display grouping for the provider, such as `Communication`.", "type": "string" }, "is_alpha": { + "description": "True when the connector is in alpha.", "example": true, "type": "boolean" }, "is_beta": { + "description": "True when the connector is in beta.", "example": true, "type": "boolean" }, "moveit_support": { + "description": "Which sync engine serves the provider. Informational: you connect every provider the same way, and `credential_schema` already reflects it.", "example": true, "type": "boolean" }, @@ -3177,24 +2501,26 @@ "type": "string" }, "rank": { - "description": "Rank is the dashboard display order (lower first); null means unranked.", + "description": "Catalog display order; lower ranks appear first. `null` when unranked.", "example": 1, "type": "integer" }, "rbac_description": { + "description": "One-line summary of which provider permissions are captured as document access rules. Not returned by this endpoint.", "type": "string" }, "rbac_support": { - "description": "RBACSupport reports whether document-level ACL capture (PRO-1684) is\nenabled for this provider (the acl_supported control-plane flag), and\nRBACDescription says in one sentence WHAT is captured, so the dashboard\ncan explain the capability instead of showing a bare boolean.", + "description": "Reserved. Always `false` on this endpoint.", "example": true, "type": "boolean" }, "supported": { + "description": "Whether the provider can be connected. Only supported providers are listed, so this is always `true`.", "example": true, "type": "boolean" }, "webhook_support": { - "description": "WebhookSupport marks a provider fed by an inbound webhook. The dashboard\nneeds it to pick the credential form: it otherwise reads moveit_support=false\nas \"classic\", and renders the single-token form instead of the provider's\ndeclared credential schema.", + "description": "True when the provider's data arrives through an inbound webhook rather than scheduled polling. Informational: `credential_schema` already describes what to send.", "example": true, "type": "boolean" } @@ -3203,13 +2529,8 @@ }, "handler.configureReq": { "properties": { - "backfill_chunk_interval_seconds": { - "description": "Internal interval for async backfill paging.", - "example": 86400, - "type": "integer" - }, "full_visibility_roles": { - "description": "FullVisibilityRoles names the HubSpot roles whose members can see every\nrecord (PRO-2036). Resolved to ids against the portal at configure time\nand stored on the account-wide resource row. A pointer so an omitted\nfield keeps the current setting while an explicit [] clears it.", + "description": "HubSpot only. Names of HubSpot roles whose members can see every record. Requires the account-wide resource (id `all`) in `resources`. Omit to keep the current setting; send `[]` to clear it. An unknown role name fails the request.", "items": { "type": "string" }, @@ -3217,12 +2538,12 @@ "uniqueItems": false }, "lookback_days": { - "description": "How far back the first sync fetches historical data. Only applies to the initial sync — subsequent syncs are incremental from the last cursor.", + "description": "How far back the first sync fetches historical data, in days. Only applies to the initial sync; later syncs are incremental from the last cursor. Above `30`, some providers fetch the older history in background chunks and the response reports `backfill: true`. Default `30`.", "example": 30, "type": "integer" }, "resources": { - "description": "Resources to activate for this connector. Each item corresponds to one entry from the Discover endpoint.", + "description": "Resources to activate for this connector, each one entry from the Discover endpoint.", "example": [ { "additional_metadata": { @@ -3247,7 +2568,7 @@ "uniqueItems": false }, "table_configs": { - "description": "TableConfigs carries per-table replication settings for MOVEIT\nconnectors whose tap reads a `table_configs` credential input (bigquery).\nConfigure merges them into the stored credential bundle before the first\nsync, so the mode chosen at selection time governs every sync from the\nstart. Optional; rejected for non-MOVEIT engines.", + "description": "Per-table replication settings for connectors that sync database tables (currently BigQuery). Saved before the first sync, so the chosen mode applies from the start. Optional; connectors that do not support it return `400`.", "items": { "$ref": "#/components/schemas/handler.tableConfigEntry" }, @@ -3263,10 +2584,12 @@ "handler.configureResponse": { "properties": { "backfill": { + "description": "`true` when older history beyond the first sync will be fetched in background chunks (see `lookback_days`).", "example": true, "type": "boolean" }, "configured": { + "description": "Number of resources activated by this request.", "example": 1, "type": "integer" }, @@ -3276,7 +2599,7 @@ "type": "string" }, "first_sync_at": { - "description": "FirstSyncAt/Message state the timing expectation: whether the first\nsync is already running (configure triggers one) or when the scheduled\none runs, so clients stop inventing their own copy (PRO-1565).", + "description": "When the connector's next scheduled sync runs (RFC 3339). `message` says whether a sync already started now.", "type": "string" }, "message": { @@ -3288,7 +2611,7 @@ "$ref": "#/components/schemas/handler.configureResponseMeta" }, "warnings": { - "description": "Warnings names resources that were saved but produced nothing when\nprobed. They are valid — the user may know a table is empty and expect it\nto fill — so they are not rejected, but they are the case that is\notherwise indistinguishable from success at every layer, so they are\nnever saved silently either.", + "description": "Resources that were saved but returned no records when probed. They stay configured and will index nothing until they have data.", "items": { "type": "string" }, @@ -3301,6 +2624,7 @@ "handler.configureResponseMeta": { "properties": { "deprecation": { + "description": "Migration notices, present when the request used a deprecated field such as `tenant_id` or `sub_tenant_id`.", "items": { "$ref": "#/components/schemas/handler.deprecationNotice" }, @@ -3312,12 +2636,8 @@ }, "handler.connectorAPIView": { "properties": { - "acl_changes_cursor": { - "description": "ACLChangesCursor is the provider permission-change feed's persisted\ncursor (PRO-1684; e.g. the Drive changes.list page token). Empty means\nuninitialized: the next cycle fetches a baseline and starts from now.\nAdvanced ONLY after every reported change was applied, so a failed\napply replays the same changes next cycle (at-least-once; the writes\nare idempotent full replacements).", - "type": "string" - }, "active_resource_count": { - "description": "ActiveResourceCount mirrors the number of non-disabled resource rows so\nlist responses can distinguish \"no resources configured yet\"\n(pending_setup) without a per-connector resources query.", + "description": "Number of active resources on this connector. Zero means none are configured yet, and `lifecycle` reads `pending_setup`.", "example": 1, "type": "integer" }, @@ -3327,31 +2647,31 @@ "type": "string" }, "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "description": "Default collection for synced data; a resource can override it. Formerly `sub_tenant_id`, which is still returned with the same value.", "example": "team_docs", "type": "string" }, "connector_id": { - "description": "Connector this resource belongs to.", + "description": "Unique identifier of the connector.", "example": "conn_abc123", "type": "string" }, "custom_instructions": { - "description": "CustomInstructions is optional free-text guidance applied when this\nconnector's documents are ingested: it steers how content is interpreted\nand indexed. Max 4000 characters; changes apply from the next sync cycle.", + "description": "Instructions that steer how this connector's synced documents are ingested and indexed. Up to 4000 characters; changes apply from the next sync.", "type": "string" }, "database": { - "description": "Database/Collection are the canonical v2 names for the deprecated\ntenant_id/sub_tenant_id wire fields. They mirror the same values so a v2\nclient sees the canonical names on responses while a legacy client keeps\nreading tenant_id/sub_tenant_id. Not persisted (dynamodbav:\"-\"): the store\nbuilds items from tenant_id/sub_tenant_id and mirrors these on load. They\nare populated at every construction point (toConnector, connectorFromItem)\nrather than via MarshalJSON so Temporal's JSON data converter round-trips\nConnector activity inputs without spuriously populating them.", + "description": "Database that receives the synced data. Formerly `tenant_id`, which is still returned with the same value.", "example": "acme_corp", "type": "string" }, "documents_dispatched": { - "description": "DocumentsDispatched is the running total of objects handed to ingestion\nacross all completed cycles. It is dispatch *activity*, not an indexed\ncount: upserts count every time they change, deletes are never\nsubtracted, and an activity retry can double-count. Suitable as an\nis-data-moving signal, never as \"N documents indexed\".", + "description": "Running total of objects sent for ingestion across all completed syncs. It shows that data is moving, not how many documents are indexed: updates count again and deletes are not subtracted.", "example": 1, "type": "integer" }, "first_data_dispatched_at": { - "description": "FirstDataDispatchedAt is set once, by the first completed cycle that\ndispatched more than zero objects. Its presence is what proves the\npipeline end to end; after it is set, an empty cycle is \"nothing changed\nat the source\", not \"still ingesting\".", + "description": "RFC3339 timestamp of the first sync that sent at least one object for ingestion. Empty until then.", "type": "string" }, "last_attempted_sync_at": { @@ -3370,22 +2690,25 @@ "type": "string" }, "lifecycle": { - "description": "Lifecycle is the derived what-is-it-doing-now field and the one status\nclients should read (PRO-1565): reconnect | syncing | pending_setup |\ningesting | active. The embedded `status` field is a scheduler-internal\nconstant (\"active\" always) kept only for compatibility, and `sync_status`\nis the narrower mid-cycle indicator. Computed at the HTTP boundary from\nthe connector's stored facts, never persisted, so it cannot disagree\nwith them.", + "description": "What the connector is doing now, and the status to read: `pending_setup` (no active resources), `ingesting` (data has not finished its first sync), `syncing` (a sync is running), `active`, `paused`, or `reconnect` (credentials were rejected or the connector is blocked; only you can fix it).", "type": "string" }, "name": { - "description": "Human-readable label for this resource.", + "description": "Human-readable label for this connector.", "example": "general", "type": "string" }, "needs_reauth": { + "description": "True when the provider rejected the connector's OAuth refresh token (expired or revoked). Reconnect the account to resume syncing; the flag clears on the next successful token refresh.", "example": true, "type": "boolean" }, "needs_reauth_at": { + "description": "RFC3339 timestamp when `needs_reauth` was set.", "type": "string" }, "needs_reauth_reason": { + "description": "Why the provider rejected the OAuth grant, when `needs_reauth` is true.", "type": "string" }, "next_sync_at": { @@ -3394,15 +2717,12 @@ "type": "string" }, "paused": { - "description": "Paused marks a connector its owner deliberately stopped (PRO-1762). It\nparks next_sync_at as SyncBlocked does, but stays a separate field:\nblocking clears itself once the cause is fixed, whereas only an explicit\nresume lifts a pause. Resources keep their committed provider_cursor, so\nresuming continues from where each stream stopped.", + "description": "True while syncs are paused. Only an explicit resume lifts a pause; each resource then continues from where it stopped.", "example": true, "type": "boolean" }, "paused_at": { - "type": "string" - }, - "paused_next_sync_at": { - "description": "PausedNextSyncAt preserves the schedule the pause displaced. Resume makes\nthe connector due immediately, so this is read back only to recover from\na pause applied by mistake.", + "description": "RFC3339 timestamp when the connector was paused.", "type": "string" }, "provider": { @@ -3416,39 +2736,33 @@ "type": "string" }, "resources_pending_first_sync": { - "description": "ResourcesPendingFirstSync counts active resources whose provider_cursor\nis still empty — resources that have never been successfully pulled.\nMOVEIT commits provider_cursor after every successful pull (even a\nzero-row one), so this self-clears one cycle after each resource first\nsyncs. Recomputed by the MOVEIT sync workflow each cycle and by the\nresource-mutating handlers, so a resource added to a long-active\nconnector re-enters the ingesting state.", + "description": "Number of active resources that have not completed their first successful sync. While above zero, `lifecycle` reads `ingesting`.", "example": 1, "type": "integer" }, "status": { - "description": "Current lifecycle or processing state.", + "description": "Always `active`; kept for compatibility. Read `lifecycle` for what the connector is doing.", "example": "completed", "type": "string" }, "sub_tenant_id": { "deprecated": true, + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "sync_blocked": { - "description": "NeedsReauth is set by MOVEIT's OAuth refresh sweep when the provider has\nrejected the connector's refresh token (`invalid_grant` — expired, revoked,\nor, for a provider with single-use tokens, already spent).\n\nIt is deliberately distinct from LastError, which records a *sync* failure.\nThis is the one failure class no amount of retrying resolves: the stored\ngrant is gone and only the tenant can mint a new one. Surfacing it as its\nown field is what lets a client show \"reconnect\" instead of a generic\n\"sync failed\", and the sweep clears it automatically on the next successful\nrotation, so a client can trust the absence of the flag as much as its\npresence.\n\nOnly ever set on OAuth-bundle connectors. A connector authenticated with a\nstatic token or with client credentials (X posts: see the `client_id` /\n`client_secret` inputs on tap-twitter) has no refresh token and therefore\ncannot reach this state at all — which is the reason to prefer that shape\nwhere a provider offers it.\nSyncBlocked marks a connector stopped by a terminal failure — one no\nretry can fix. The scheduler skips it and next_sync_at is parked a\ncentury out; only a credential or config update clears it. Distinct from\nNeedsReauth, which is the OAuth sweep's own narrower signal: this covers\nany provider rejection of the stored credentials, including static keys\nthat have no refresh token to sweep.", + "description": "True when a failure that retrying cannot fix, such as rejected credentials, stopped scheduled syncs. Updating the credentials or configuration clears it and syncs resume.", "example": true, "type": "boolean" }, "sync_blocked_at": { + "description": "RFC3339 timestamp when `sync_blocked` was set.", "type": "string" }, "sync_blocked_reason": { - "type": "string" - }, - "sync_cycles_completed": { - "description": "SyncCyclesCompleted counts successfully completed sync cycles. Bounded\nuse only: it lets DeriveLifecycle stop reporting \"ingesting\" after a few\nclean-but-empty cycles on a source that genuinely has nothing to pull.", - "example": 1, - "type": "integer" - }, - "sync_engine": { - "description": "SyncEngine is \"classic\" (default, empty treated as classic) or \"moveit\".\nSee the SyncEngine* constants; the scheduler branches on it.", + "description": "Error that blocked the connector, when `sync_blocked` is true. Up to 1000 characters.", "type": "string" }, "sync_interval_seconds": { @@ -3463,6 +2777,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -3470,30 +2785,6 @@ }, "type": "object" }, - "handler.connectorCatalogResponse": { - "properties": { - "connectors": { - "example": [ - { - "is_alpha": true, - "is_beta": true, - "moveit_support": true, - "provider": "slack", - "rank": 1, - "rbac_support": true, - "supported": true, - "webhook_support": true - } - ], - "items": { - "$ref": "#/components/schemas/handler.catalogConnector" - }, - "type": "array", - "uniqueItems": false - } - }, - "type": "object" - }, "handler.connectorCreateReq": { "properties": { "auth_type": { @@ -3515,11 +2806,11 @@ "type": "object" }, "custom_instructions": { - "description": "CustomInstructions optionally steers how this connector's synced\ndocuments are ingested and indexed. Max 4000 characters; editable later\nvia PATCH.", + "description": "Instructions that steer how this connector's synced documents are ingested and indexed. Up to 4000 characters; editable later.", "type": "string" }, "database": { - "description": "Database/Collection are the canonical v2 names; TenantID/SubTenantID are\ntheir deprecated aliases, reconciled by the TenantAliases middleware before\nbinding so TenantID is always populated. Neither is marked binding:required\n(mirroring TenantCreateRequest): a caller may send either spelling, and the\ntenant scope is validated downstream by resolveTenant. Requiring tenant_id\nhere would force the generated SDK to demand the deprecated field.", + "description": "Database that receives the synced data. Required; the deprecated alias `tenant_id` is also accepted.", "example": "acme_corp", "type": "string" }, @@ -3540,7 +2831,7 @@ }, "sub_tenant_id": { "deprecated": true, - "description": "deprecated: use collection", + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" @@ -3552,7 +2843,7 @@ }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -3565,12 +2856,8 @@ }, "handler.connectorCreateResponse": { "properties": { - "acl_changes_cursor": { - "description": "ACLChangesCursor is the provider permission-change feed's persisted\ncursor (PRO-1684; e.g. the Drive changes.list page token). Empty means\nuninitialized: the next cycle fetches a baseline and starts from now.\nAdvanced ONLY after every reported change was applied, so a failed\napply replays the same changes next cycle (at-least-once; the writes\nare idempotent full replacements).", - "type": "string" - }, "active_resource_count": { - "description": "ActiveResourceCount mirrors the number of non-disabled resource rows so\nlist responses can distinguish \"no resources configured yet\"\n(pending_setup) without a per-connector resources query.", + "description": "Number of active resources on this connector. Zero means none are configured yet, and `lifecycle` reads `pending_setup`.", "example": 1, "type": "integer" }, @@ -3580,35 +2867,35 @@ "type": "string" }, "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "description": "Default collection for synced data; a resource can override it. Formerly `sub_tenant_id`, which is still returned with the same value.", "example": "team_docs", "type": "string" }, "connector_id": { - "description": "Connector this resource belongs to.", + "description": "Unique identifier of the connector.", "example": "conn_abc123", "type": "string" }, "custom_instructions": { - "description": "CustomInstructions is optional free-text guidance applied when this\nconnector's documents are ingested: it steers how content is interpreted\nand indexed. Max 4000 characters; changes apply from the next sync cycle.", + "description": "Instructions that steer how this connector's synced documents are ingested and indexed. Up to 4000 characters; changes apply from the next sync.", "type": "string" }, "database": { - "description": "Database/Collection are the canonical v2 names for the deprecated\ntenant_id/sub_tenant_id wire fields. They mirror the same values so a v2\nclient sees the canonical names on responses while a legacy client keeps\nreading tenant_id/sub_tenant_id. Not persisted (dynamodbav:\"-\"): the store\nbuilds items from tenant_id/sub_tenant_id and mirrors these on load. They\nare populated at every construction point (toConnector, connectorFromItem)\nrather than via MarshalJSON so Temporal's JSON data converter round-trips\nConnector activity inputs without spuriously populating them.", + "description": "Database that receives the synced data. Formerly `tenant_id`, which is still returned with the same value.", "example": "acme_corp", "type": "string" }, "documents_dispatched": { - "description": "DocumentsDispatched is the running total of objects handed to ingestion\nacross all completed cycles. It is dispatch *activity*, not an indexed\ncount: upserts count every time they change, deletes are never\nsubtracted, and an activity retry can double-count. Suitable as an\nis-data-moving signal, never as \"N documents indexed\".", + "description": "Running total of objects sent for ingestion across all completed syncs. It shows that data is moving, not how many documents are indexed: updates count again and deletes are not subtracted.", "example": 1, "type": "integer" }, "first_data_dispatched_at": { - "description": "FirstDataDispatchedAt is set once, by the first completed cycle that\ndispatched more than zero objects. Its presence is what proves the\npipeline end to end; after it is set, an empty cycle is \"nothing changed\nat the source\", not \"still ingesting\".", + "description": "RFC3339 timestamp of the first sync that sent at least one object for ingestion. Empty until then.", "type": "string" }, "first_sync_at": { - "description": "FirstSyncAt is when the first scheduled sync runs (RFC3339).", + "description": "RFC3339 timestamp when the first scheduled sync runs.", "type": "string" }, "last_attempted_sync_at": { @@ -3627,27 +2914,30 @@ "type": "string" }, "lifecycle": { - "description": "Lifecycle is the derived what-is-it-doing-now field and the one status\nclients should read (PRO-1565): reconnect | syncing | pending_setup |\ningesting | active. The embedded `status` field is a scheduler-internal\nconstant (\"active\" always) kept only for compatibility, and `sync_status`\nis the narrower mid-cycle indicator. Computed at the HTTP boundary from\nthe connector's stored facts, never persisted, so it cannot disagree\nwith them.", + "description": "What the connector is doing now, and the status to read: `pending_setup` (no active resources), `ingesting` (data has not finished its first sync), `syncing` (a sync is running), `active`, `paused`, or `reconnect` (credentials were rejected or the connector is blocked; only you can fix it).", "type": "string" }, "message": { - "description": "Message is a human-readable expectation, safe to show verbatim.", + "description": "Human-readable note on when data will start to sync, safe to show to users as is.", "example": "Success", "type": "string" }, "name": { - "description": "Human-readable label for this resource.", + "description": "Human-readable label for this connector.", "example": "general", "type": "string" }, "needs_reauth": { + "description": "True when the provider rejected the connector's OAuth refresh token (expired or revoked). Reconnect the account to resume syncing; the flag clears on the next successful token refresh.", "example": true, "type": "boolean" }, "needs_reauth_at": { + "description": "RFC3339 timestamp when `needs_reauth` was set.", "type": "string" }, "needs_reauth_reason": { + "description": "Why the provider rejected the OAuth grant, when `needs_reauth` is true.", "type": "string" }, "next_sync_at": { @@ -3656,15 +2946,12 @@ "type": "string" }, "paused": { - "description": "Paused marks a connector its owner deliberately stopped (PRO-1762). It\nparks next_sync_at as SyncBlocked does, but stays a separate field:\nblocking clears itself once the cause is fixed, whereas only an explicit\nresume lifts a pause. Resources keep their committed provider_cursor, so\nresuming continues from where each stream stopped.", + "description": "True while syncs are paused. Only an explicit resume lifts a pause; each resource then continues from where it stopped.", "example": true, "type": "boolean" }, "paused_at": { - "type": "string" - }, - "paused_next_sync_at": { - "description": "PausedNextSyncAt preserves the schedule the pause displaced. Resume makes\nthe connector due immediately, so this is read back only to recover from\na pause applied by mistake.", + "description": "RFC3339 timestamp when the connector was paused.", "type": "string" }, "provider": { @@ -3678,39 +2965,33 @@ "type": "string" }, "resources_pending_first_sync": { - "description": "ResourcesPendingFirstSync counts active resources whose provider_cursor\nis still empty — resources that have never been successfully pulled.\nMOVEIT commits provider_cursor after every successful pull (even a\nzero-row one), so this self-clears one cycle after each resource first\nsyncs. Recomputed by the MOVEIT sync workflow each cycle and by the\nresource-mutating handlers, so a resource added to a long-active\nconnector re-enters the ingesting state.", + "description": "Number of active resources that have not completed their first successful sync. While above zero, `lifecycle` reads `ingesting`.", "example": 1, "type": "integer" }, "status": { - "description": "Current lifecycle or processing state.", + "description": "Always `active`; kept for compatibility. Read `lifecycle` for what the connector is doing.", "example": "completed", "type": "string" }, "sub_tenant_id": { "deprecated": true, + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "sync_blocked": { - "description": "NeedsReauth is set by MOVEIT's OAuth refresh sweep when the provider has\nrejected the connector's refresh token (`invalid_grant` — expired, revoked,\nor, for a provider with single-use tokens, already spent).\n\nIt is deliberately distinct from LastError, which records a *sync* failure.\nThis is the one failure class no amount of retrying resolves: the stored\ngrant is gone and only the tenant can mint a new one. Surfacing it as its\nown field is what lets a client show \"reconnect\" instead of a generic\n\"sync failed\", and the sweep clears it automatically on the next successful\nrotation, so a client can trust the absence of the flag as much as its\npresence.\n\nOnly ever set on OAuth-bundle connectors. A connector authenticated with a\nstatic token or with client credentials (X posts: see the `client_id` /\n`client_secret` inputs on tap-twitter) has no refresh token and therefore\ncannot reach this state at all — which is the reason to prefer that shape\nwhere a provider offers it.\nSyncBlocked marks a connector stopped by a terminal failure — one no\nretry can fix. The scheduler skips it and next_sync_at is parked a\ncentury out; only a credential or config update clears it. Distinct from\nNeedsReauth, which is the OAuth sweep's own narrower signal: this covers\nany provider rejection of the stored credentials, including static keys\nthat have no refresh token to sweep.", + "description": "True when a failure that retrying cannot fix, such as rejected credentials, stopped scheduled syncs. Updating the credentials or configuration clears it and syncs resume.", "example": true, "type": "boolean" }, "sync_blocked_at": { + "description": "RFC3339 timestamp when `sync_blocked` was set.", "type": "string" }, "sync_blocked_reason": { - "type": "string" - }, - "sync_cycles_completed": { - "description": "SyncCyclesCompleted counts successfully completed sync cycles. Bounded\nuse only: it lets DeriveLifecycle stop reporting \"ingesting\" after a few\nclean-but-empty cycles on a source that genuinely has nothing to pull.", - "example": 1, - "type": "integer" - }, - "sync_engine": { - "description": "SyncEngine is \"classic\" (default, empty treated as classic) or \"moveit\".\nSee the SyncEngine* constants; the scheduler branches on it.", + "description": "Error that blocked the connector, when `sync_blocked` is true. Up to 1000 characters.", "type": "string" }, "sync_interval_seconds": { @@ -3725,6 +3006,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -3740,7 +3022,7 @@ "type": "string" }, "deleted": { - "description": "Whether this specific item was deleted.", + "description": "Whether the connector was deleted.", "example": true, "type": "boolean" } @@ -3748,23 +3030,25 @@ "type": "object" }, "handler.connectorLimitView": { - "description": "ConnectorLimit is present when the org has used its plan's connector\nallowance (Free: 3) and the caps mode enforces it: creating another\nconnector is refused with 402. Connectors already past the allowance\nkeep syncing; the limit applies to creating one. Absent otherwise, and\nsent with the health rollups only.", + "description": "The plan's limit on the number of connectors.", "properties": { "count": { - "description": "Total number of items returned.", + "description": "Number of connectors the organization has.", "example": 12, "type": "integer" }, "limit": { + "description": "Number of connectors the plan allows.", "example": 1, "type": "integer" }, "message": { - "description": "Human-readable result message.", + "description": "Human-readable explanation, safe to show to users as is.", "example": "Success", "type": "string" }, "plan": { + "description": "Plan the organization is on.", "type": "string" } }, @@ -3773,7 +3057,12 @@ "handler.connectorListResponse": { "properties": { "connector_limit": { - "$ref": "#/components/schemas/handler.connectorLimitView", + "allOf": [ + { + "$ref": "#/components/schemas/handler.connectorLimitView" + } + ], + "description": "Present when the organization has used its plan's connector allowance, so creating another connector is refused with `402`. Existing connectors keep syncing. Returned only with `include=health`.", "example": { "count": 12, "limit": 1, @@ -3781,6 +3070,7 @@ } }, "connectors": { + "description": "Connectors in your organization.", "example": [ { "active_resource_count": 1, @@ -3802,7 +3092,6 @@ "status": "completed", "sub_tenant_id": "sub_tenant_4567", "sync_blocked": true, - "sync_cycles_completed": 1, "sync_interval_seconds": 3600, "sync_status": "idle", "tenant_id": "tenant_1234" @@ -3818,11 +3107,16 @@ "additionalProperties": { "type": "string" }, - "description": "Health maps connector_id to its rollup (healthy | degraded | failed |\nchecking | capped), present only when the caller asks for\n`?include=health`. A connector missing from the map has an unknown\nrollup — its resources could not be read — which clients must not\nrender as a failure. `capped` is not a rollup of the connector: it is\noverlaid on a healthy, degraded or checking one when the org is at a\nplan cap, and PlanCap says which.", + "description": "Map from `connector_id` to a health summary: `healthy`, `degraded`, `failed`, `checking` or `capped`. Returned only with `include=health`. A connector missing from the map has unknown health and should not be shown as failed. `capped` means the organization is at a plan limit; see `plan_cap`.", "type": "object" }, "plan_cap": { - "$ref": "#/components/schemas/handler.planCapView", + "allOf": [ + { + "$ref": "#/components/schemas/handler.planCapView" + } + ], + "description": "Present when the organization is at a plan limit and every connector sync is skipped until the monthly usage resets or the plan changes. Returned only with `include=health`.", "example": { "message": "Success" } @@ -3830,109 +3124,16 @@ }, "type": "object" }, - "handler.connectorPauseResponse": { - "properties": { - "connector_id": { - "description": "Connector this resource belongs to.", - "example": "conn_abc123", - "type": "string" - }, - "paused": { - "example": true, - "type": "boolean" - }, - "paused_at": { - "type": "string" - } - }, - "type": "object" - }, - "handler.connectorResourceStatus": { - "properties": { - "acl_warning": { - "description": "ACLWarning explains why permission capture could not read this\nresource. Capture fails OPEN, so while this is set the resource is\nreadable by EVERY caller regardless of the ACL they send. Deliberately\nnot folded into Status: the resource is syncing fine and its content is\ncurrent, so calling it failed would be wrong and would train people to\nignore a red badge. It is a separate signal because it needs a separate\nreaction (grant the missing permission, or set an access rule).", - "type": "string" - }, - "acl_warning_at": { - "description": "ACLWarningAt is when this warning was last CHANGED (RFC3339), not when\nthe failure was last observed. An unchanged warning is deliberately not\nrewritten every cycle, so treat this as \"open since\", not \"checked at\".", - "type": "string" - }, - "action": { - "description": "Action is what the user must do, when there is something they can do.", - "type": "string" - }, - "checked_at": { - "type": "string" - }, - "display_name": { - "description": "Human-readable name for this resource.", - "example": "general", - "type": "string" - }, - "http_status": { - "description": "HTTPStatus is the provider's response code when one was reported.", - "example": 1, - "type": "integer" - }, - "last_row_count": { - "description": "LastRowCount is the rows produced by the last sync.", - "example": 1, - "type": "integer" - }, - "message": { - "description": "Message is the provider's own words when Status is failed.", - "example": "Success", - "type": "string" - }, - "page_acl_warning": { - "description": "PageACLWarning reports that individual PAGES inside this resource could\nnot have their own restrictions resolved and were opened to every caller.\nDistinct from ACLWarning above, which is about the resource itself: a\nresource can capture perfectly while pages inside it fail, and a healthy\nresource capture clears ACLWarning, so sharing one field would blank this\nevery cycle and report all-clear while pages are still open.", - "type": "string" - }, - "page_acl_warning_at": { - "description": "PageACLWarningAt is when PageACLWarning last CHANGED (RFC3339).", - "type": "string" - }, - "resource_id": { - "description": "Resource identifier from the Discover endpoint.", - "example": "C0123456789", - "type": "string" - }, - "retryable": { - "description": "Retryable is set only for a failed resource: false for a provider\nrejection the user must fix (403, 404, a misconfigured table), true for\nsomething that may clear on its own.", - "example": true, - "type": "boolean" - }, - "status": { - "description": "Status is one of ok | empty | failed | checking | unknown.", - "example": "completed", - "type": "string" - }, - "sync_blocked": { - "description": "SyncBlocked reports that this resource has stopped syncing. Distinct from\na failed status: a resource can fail a cycle and be retried, and the\ndifference between \"failing\" and \"given up on\" is the one a user needs to\nact on.", - "example": true, - "type": "boolean" - }, - "sync_blocked_at": { - "description": "SyncBlockedAt is when it stopped (RFC3339).", - "type": "string" - }, - "sync_blocked_reason": { - "description": "SyncBlockedReason is why it stopped, preserved from the failure that\nstopped it so it survives later syncs overwriting the health block.", - "type": "string" - } - }, - "type": "object" - }, "handler.connectorResourcesResponse": { "properties": { "resources": { + "description": "Resources configured on the connector, with their current sync state.", "example": [ { "additional_metadata": { "author": "ada", "doc_version": 3 }, - "backfill_chunk_interval_seconds": 86400, "backfill_oldest": "2026-06-01T00:00:00Z", "connector_id": "conn_abc123", "display_name": "general", @@ -3962,185 +3163,23 @@ }, "type": "object" }, - "handler.connectorStatusError": { - "description": "Error is the connector-level failure: a rejected credential, a blocked\nconnector, or a latest sync cycle that failed as a whole. Absent when the\ntrouble is confined to individual resources — those carry their own\nmessages below.", - "properties": { - "action": { - "description": "Action is what the user must do, when there is something they can do.", - "type": "string" - }, - "detected_at": { - "description": "DetectedAt is when the failure was observed (RFC3339).", - "type": "string" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "retryable": { - "description": "Retryable reports whether waiting can fix this. False means only the user\ncan: a rejected credential is not a transient error, and telling someone\nto retry a dead OAuth grant wastes their time.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, - "handler.connectorStatusResponse": { - "properties": { - "connector_id": { - "description": "Connector this resource belongs to.", - "example": "conn_abc123", - "type": "string" - }, - "error": { - "$ref": "#/components/schemas/handler.connectorStatusError", - "description": "Error message, empty string on success.", - "example": { - "message": "Success", - "retryable": true - } - }, - "last_attempted_sync_at": { - "description": "RFC3339 timestamp of the most recent sync attempt (successful or not).", - "example": "2026-07-02T17:00:00Z", - "type": "string" - }, - "last_successful_sync_at": { - "description": "RFC3339 timestamp of the last successful sync completion.", - "example": "2026-07-02T17:00:00Z", - "type": "string" - }, - "lifecycle": { - "description": "Lifecycle is the derived what-is-it-doing-now field (PRO-1565):\nreconnect | syncing | pending_setup | ingesting | active. Orthogonal to\nthe health rollup above — a connector can be ingesting and healthy, or\nactive and degraded.", - "type": "string" - }, - "next_sync_at": { - "description": "RFC3339 timestamp when the next scheduled sync will run.", - "example": "2026-07-02T18:00:00Z", - "type": "string" - }, - "plan_cap": { - "$ref": "#/components/schemas/handler.planCapView", - "example": { - "message": "Success" - } - }, - "provider": { - "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", - "example": "slack", - "type": "string" - }, - "resources": { - "description": "Resources is one entry per configured resource, always non-nil so it\nserialises as [] rather than null.", - "example": [ - { - "display_name": "general", - "http_status": 1, - "last_row_count": 1, - "message": "Success", - "resource_id": "C0123456789", - "retryable": true, - "status": "completed", - "sync_blocked": true - } - ], - "items": { - "$ref": "#/components/schemas/handler.connectorResourceStatus" - }, - "type": "array", - "uniqueItems": false - }, - "status": { - "description": "Status is the rollup: healthy | degraded | failed | checking, or capped\nwhen the org is at an enforced plan cap and the rollup was healthy,\ndegraded or checking (PlanCap then says which cap). It is the worst of\nthe credential state and every resource state.", - "example": "completed", - "type": "string" - }, - "sync_status": { - "description": "SyncStatus is the in-progress indicator (\"syncing\"/\"idle\"), orthogonal to\nStatus — a connector can be mid-sync and degraded at the same time.", - "example": "idle", - "type": "string" - } - }, - "type": "object" - }, "handler.connectorSyncResponse": { "properties": { "run_id": { + "description": "Identifier of this particular sync run.", "type": "string" }, "workflow_id": { + "description": "Identifier of the sync job for this connector.", "type": "string" } }, "type": "object" }, - "handler.connectorUpdateReq": { - "properties": { - "credentials": { - "additionalProperties": {}, - "description": "Provider-specific credentials (typically `{\"api_token\": \"...\"}` or `{\"access_token\": \"...\"}`).", - "example": { - "api_token": "xoxb-..." - }, - "type": "object" - }, - "custom_instructions": { - "description": "CustomInstructions replaces the guidance applied when this connector's\ndocuments are ingested. Omitted leaves it unchanged; an explicit empty\nstring clears it. Max 4000 characters; applies from the next sync cycle.", - "type": "string" - }, - "sync_interval_seconds": { - "description": "How frequently the scheduler triggers incremental syncs, in seconds. Bounded per provider; send 0 or omit to use the provider default. Change it later with PATCH /connectors/{id}.", - "example": 3600, - "type": "integer" - } - }, - "type": "object" - }, - "handler.connectorUpdateResponse": { - "properties": { - "connector_id": { - "description": "Connector this resource belongs to.", - "example": "conn_abc123", - "type": "string" - }, - "credentials_updated": { - "description": "CredentialsUpdated reports that the stored credential bundle was\nre-written (and any needs-reauth flag cleared) by this request.", - "example": true, - "type": "boolean" - }, - "custom_instructions_updated": { - "description": "CustomInstructionsUpdated reports that the steering text was rewritten\n(or cleared) by this request; it takes effect from the next sync cycle.", - "example": true, - "type": "boolean" - }, - "max_sync_interval_seconds": { - "description": "Largest sync_interval_seconds this connector's provider allows.", - "example": 604800, - "type": "integer" - }, - "min_sync_interval_seconds": { - "description": "Smallest sync_interval_seconds this connector's provider allows. Values below it are rejected, never clamped.", - "example": 300, - "type": "integer" - }, - "next_sync_at": { - "description": "RFC3339 timestamp when the next scheduled sync will run.", - "example": "2026-07-02T18:00:00Z", - "type": "string" - }, - "sync_interval_seconds": { - "description": "How frequently the scheduler triggers incremental syncs, in seconds. Bounded per provider; send 0 or omit to use the provider default. Change it later with PATCH /connectors/{id}.", - "example": 3600, - "type": "integer" - } - }, - "type": "object" - }, "handler.contextMetadataUpdateRequest": { "properties": { "acl": { - "description": "ACL, when present, REPLACES the source's access-control list without\nre-ingestion (PRO-1684): pass the COMPLETE new allow-list (adding a\nthird user means sending all three), an empty list to make the source\nprivate, or [\"__public__\"] to open it to every identified caller. A\npointer so omitted (nil, ACL untouched) is distinguishable from an\nexplicit empty list (private).\nACL uses RawMessage so the handler can tell three wire states apart:\nabsent (leave the stored ACL untouched), explicit null (revoke to\nnobody, JSON-merge-patch semantics), and a list (replace). A plain\n*[]string cannot: encoding/json leaves the pointer nil for BOTH\nabsent and null, which silently ignored an explicit null revocation.", + "description": "Replaces the context's access-control list; it does not merge. Send the complete new list, `[]` or `null` to make the context private, or `[\"__public__\"]` to make it visible to every identified caller. Omit it to leave the list unchanged.", "items": { "type": "string" }, @@ -4149,7 +3188,7 @@ }, "additional_metadata": { "additionalProperties": {}, - "description": "Free-form key-value pairs to merge into the source's `additional_metadata`. The only accepted spelling for document metadata on this endpoint. Capped at 1 KiB, measured on the compact JSON encoding of the whole map in UTF-8 bytes — keys, quotes, commas and braces count toward the budget. Over-cap returns 400 with the actual byte count.", + "description": "Free-form values to merge into the context's `custom_attributes` (named `additional_metadata` on older responses). At most 1 KiB for the whole map, measured as compact UTF-8 JSON; over the cap returns 400.", "example": { "author": "ada", "doc_version": 3 @@ -4157,18 +3196,18 @@ "type": "object" }, "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "description": "Collection that holds the context. Required; this endpoint does not default it. Formerly `sub_tenant_id`.", "example": "team_docs", "type": "string" }, "database": { - "description": "Database/Collection are the canonical v2 names; TenantID/SubTenantID are\ntheir deprecated aliases. The TenantAliases middleware reconciles them in\nthe request body before binding, so the handler reads TenantID/SubTenantID.", + "description": "Database that holds the context. Required. Formerly `tenant_id`.", "example": "acme_corp", "type": "string" }, "database_metadata": { "additionalProperties": {}, - "description": "Schema-backed metadata fields to merge into the source's `metadata` (database metadata). Canonical name; `tenant_metadata` is a deprecated alias. Capped at 16 KiB, measured on the compact JSON encoding of the whole map in UTF-8 bytes — keys, quotes, commas and braces count toward the budget. Over-cap returns 400 with the actual byte count.", + "description": "Values to merge into the context's `attributes` (named `metadata` on older responses). Keys must be declared in the database schema when it has one. At most 16 KiB for the whole map, measured as compact UTF-8 JSON; over the cap returns 400.", "example": { "department": "legal", "priority": 7 @@ -4178,20 +3217,20 @@ "document_metadata": { "additionalProperties": {}, "deprecated": true, - "description": "Not accepted on this endpoint. Sending any non-null value returns 400 (`document_metadata is not accepted; use additional_metadata`), regardless of size. Use `additional_metadata` instead. Accepted as an alias on /context/ingest only.", + "description": "Deprecated: not accepted here; any non-null value returns 400. Use `additional_metadata`.", "type": "object", "x-deprecated": "true" }, "sub_tenant_id": { "deprecated": true, - "description": "deprecated: use collection", + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -4199,7 +3238,7 @@ "tenant_metadata": { "additionalProperties": {}, "deprecated": true, - "description": "Deprecated alias for `database_metadata`, still accepted here; `database_metadata` wins when both are sent. Capped at 16 KiB, measured on the compact JSON encoding of the whole map in UTF-8 bytes — keys, quotes, commas and braces count toward the budget. Over-cap returns 400 with the actual byte count.", + "description": "Deprecated: use `database_metadata`. Still accepted; `database_metadata` wins when both are sent.", "example": { "department": "legal", "priority": 7 @@ -4210,21 +3249,6 @@ }, "type": "object" }, - "handler.credentialsUpdateResponse": { - "properties": { - "connector_id": { - "description": "Connector this resource belongs to.", - "example": "conn_abc123", - "type": "string" - }, - "updated": { - "description": "Whether the source metadata was updated.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, "handler.deprecationNotice": { "properties": { "deprecated": { @@ -4255,43 +3279,17 @@ }, "type": "object" }, - "handler.discoverPreviewReq": { + "handler.discoverResponseBody": { "properties": { - "auth_type": { - "description": "Authentication method for the provider connection (e.g. `api_token`, `oauth`).", - "example": "api_token", - "type": "string" + "has_more": { + "description": "Present and `true` when more pages remain. Only on paginated requests (`limit` or `cursor`); fetch the next page with `next_cursor`.", + "example": true, + "type": "boolean" }, - "credentials": { - "additionalProperties": {}, - "description": "Provider-specific credentials (typically `{\"api_token\": \"...\"}` or `{\"access_token\": \"...\"}`).", - "example": { - "api_token": "xoxb-..." - }, - "type": "object" - }, - "provider": { - "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", - "example": "slack", - "type": "string" - } - }, - "required": [ - "credentials", - "provider" - ], - "type": "object" - }, - "handler.discoverResponseBody": { - "properties": { - "has_more": { - "example": true, - "type": "boolean" - }, - "next_cursor": { - "description": "Opaque pagination cursor for the next page; null or absent when no more pages.", - "example": "eyJvZmZzZXQiOjUwfQ==", - "type": "string" + "next_cursor": { + "description": "Opaque pagination cursor for the next page; null or absent when no more pages.", + "example": "eyJvZmZzZXQiOjUwfQ==", + "type": "string" }, "provider": { "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", @@ -4299,6 +3297,7 @@ "type": "string" }, "resources": { + "description": "Resources available to the connector's credentials. Pass an entry's `id` (as `resource_id`) and `resource_type` to Configure.", "example": [ { "id": "HydraDoc1234", @@ -4328,6 +3327,7 @@ }, "metadata": { "additionalProperties": {}, + "description": "Provider-specific details about this resource, for display. Shape varies by provider.", "example": { "department": "finance", "priority": 7 @@ -4347,64 +3347,6 @@ }, "type": "object" }, - "handler.instructionsResponse": { - "properties": { - "collections": { - "additionalProperties": { - "type": "string" - }, - "description": "Collections maps collection name to that collection's own instructions.", - "example": [ - "team_docs", - "engineering" - ], - "type": "object" - }, - "custom_instructions": { - "description": "CustomInstructions applies to every document ingested into the database.", - "type": "string" - }, - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "tenant_id": { - "deprecated": true, - "description": "TenantID mirrors Database as a deprecated alias, matching every other v2\ntenant response.", - "example": "acme_corp", - "type": "string", - "x-deprecated": "true" - } - }, - "type": "object" - }, - "handler.instructionsUpdateReq": { - "properties": { - "collections": { - "additionalProperties": { - "type": [ - "string", - "null" - ] - }, - "description": "Collections merges per-collection instructions into the stored set: a\ncollection present with a value is set, a collection present with \"\" or\nnull is cleared, and a collection absent from the map is left untouched.\nMerge rather than replace so two people editing different collections\ncannot silently delete each other's work.", - "example": { - "engineering": null, - "team_docs": "Summarise decisions and who owns them." - }, - "type": "object" - }, - "custom_instructions": { - "description": "CustomInstructions sets the database-wide instructions. Send \"\" to clear.", - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, "handler.metadataSchemaUpdateResponse": { "properties": { "added_fields": { @@ -4426,6 +3368,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "acme_corp", "type": "string", "x-deprecated": "true" @@ -4434,17 +3377,19 @@ "type": "object" }, "handler.planCapView": { - "description": "PlanCap is present when the org is at a plan cap the caps mode\nenforces; every sync is skipped until the month resets or the plan\nchanges.", + "description": "The plan limit that is currently stopping syncs.", "properties": { "message": { - "description": "Human-readable result message.", + "description": "Human-readable explanation of the limit, the same message the ingest endpoints return.", "example": "Success", "type": "string" }, "meter": { + "description": "Which limit was reached: `tokens` or `storage`.", "type": "string" }, "plan": { + "description": "Plan the organization is on.", "type": "string" } }, @@ -4453,6 +3398,7 @@ "handler.providerListResponse": { "properties": { "providers": { + "description": "Providers you can connect, in catalog display order.", "example": [ { "is_alpha": true, @@ -4477,7 +3423,7 @@ "handler.resourceCreateReq": { "properties": { "acl": { - "description": "ACL restricts every object synced from this resource to the listed\nprincipals (see resourceMapping.ACL). Omitted means unrestricted.", + "description": "Restricts every object synced from this resource to the listed principals (emails or prefixed principals). Omitted means unrestricted. See Access Control.", "items": { "type": "string" }, @@ -4486,7 +3432,7 @@ }, "additional_metadata": { "additionalProperties": {}, - "description": "Key-value pairs merged into document metadata on every synced object from this resource. Capped at 1 KiB, measured on the compact JSON encoding of the whole map in UTF-8 bytes — keys, quotes, commas and braces count toward the budget. The cap is applied when synced objects are ingested, not to this request.", + "description": "Key-value pairs merged into the custom attributes of every object synced from this resource. Limited to 1 KiB of compact JSON (UTF-8 bytes, whole map); the limit is applied when synced objects are ingested, not to this request.", "example": { "author": "ada", "doc_version": 3 @@ -4494,13 +3440,15 @@ "type": "object" }, "collection_override": { + "description": "Routes objects synced from this resource into a specific collection. Empty means the connector's collection.", "type": "string" }, "custom_instructions": { - "description": "CustomInstructions optionally steers how documents synced from this\nresource are ingested and indexed. When set it replaces the\nconnector-level custom_instructions for this resource; empty inherits\nthe connector's value. Max 4000 characters.", + "description": "Instructions that steer how documents synced from this resource are ingested and indexed. When set, replaces the connector's `custom_instructions` for this resource; empty inherits the connector's value. Up to 4000 characters.", "type": "string" }, "database_override": { + "description": "Routes objects synced from this resource into a different database. Empty means the connector's database.", "type": "string" }, "display_name": { @@ -4518,7 +3466,7 @@ }, "metadata": { "additionalProperties": {}, - "description": "Key-value pairs merged into tenant metadata on every synced object from this resource. Capped at 16 KiB, measured on the compact JSON encoding of the whole map in UTF-8 bytes — keys, quotes, commas and braces count toward the budget. The cap is applied when synced objects are ingested, not to this request.", + "description": "Key-value pairs merged into the attributes of every object synced from this resource. Limited to 16 KiB of compact JSON (UTF-8 bytes, whole map); the limit is applied when synced objects are ingested, not to this request.", "example": { "department": "finance", "priority": 7 @@ -4551,7 +3499,7 @@ }, "tenant_id_override": { "deprecated": true, - "description": "DatabaseOverride/CollectionOverride are the canonical v2 names;\nTenantIDOverride/SubTenantIDOverride are their deprecated aliases.", + "description": "Deprecated: use `database_override`.", "type": "string", "x-deprecated": "true" } @@ -4569,7 +3517,7 @@ "type": "string" }, "deleted": { - "description": "Whether this specific item was deleted.", + "description": "Whether the resource was deleted.", "example": true, "type": "boolean" }, @@ -4584,7 +3532,7 @@ "handler.resourceMapping": { "properties": { "acl": { - "description": "ACL restricts every object synced from this resource to the listed\nprincipals (emails, or prefixed principals, see the query-side\nuser_email parameter). Omitted means unrestricted. An explicitly empty\nlist means private (visible only to unfiltered queries).", + "description": "Restricts every object synced from this resource to the listed principals (emails or prefixed principals, as in the query-side `user_email`). Omitted means unrestricted; an empty list makes the objects private. See Access Control.", "items": { "type": "string" }, @@ -4593,7 +3541,7 @@ }, "additional_metadata": { "additionalProperties": {}, - "description": "AdditionalMetadata is merged into the additional_metadata layer of every\nobject synced from this resource. Provider-generated fields take precedence.", + "description": "Key-value pairs merged into the custom attributes of every object synced from this resource. Free-form, no schema required. Provider-generated fields win on conflict.", "example": { "author": "ada", "doc_version": 3 @@ -4601,22 +3549,22 @@ "type": "object" }, "collection": { - "description": "Collection is the canonical v2 name for the per-resource sub-tenant\noverride: routes synced objects from this resource into a specific\ncollection. Empty means the resource inherits the connector collection.\nSubTenantID is the deprecated alias for this field, reconciled by\nConfigure before toResource runs.", + "description": "Routes objects synced from this resource into a specific collection. Overrides the connector-level `collection`; empty means the connector's collection.", "example": "team_docs", "type": "string" }, "custom_instructions": { - "description": "CustomInstructions optionally steers how documents synced from this\nresource are ingested and indexed. When set it replaces the\nconnector-level custom_instructions for this resource; empty inherits\nthe connector's value. Max 4000 characters.", + "description": "Instructions that steer how documents synced from this resource are ingested and indexed. When set, replaces the connector's `custom_instructions` for this resource; empty inherits the connector's value. Up to 4000 characters.", "type": "string" }, "database": { - "description": "Database is the canonical v2 name for the per-resource tenant override:\nroutes synced objects from this resource into a specific database.\nEmpty means the resource inherits the connector database. TenantID is the\ndeprecated alias for this field, reconciled by Configure before\ntoResource runs.", + "description": "Routes objects synced from this resource into a specific database. Empty means the connector's database.", "example": "acme_corp", "type": "string" }, "metadata": { "additionalProperties": {}, - "description": "Metadata is merged into the tenant metadata layer of every object synced\nfrom this resource. System fields (connector_id, provider) take precedence.", + "description": "Key-value pairs merged into the attributes of every object synced from this resource. Undeclared keys are stored, but only keys declared in the database's metadata schema are indexed for filtering. `connector_id` and `provider` always win on conflict.", "example": { "department": "finance", "priority": 7 @@ -4640,13 +3588,13 @@ }, "sub_tenant_id": { "deprecated": true, - "description": "Routes synced objects from this resource into a specific sub-tenant\npartition. Overrides the connector-level sub_tenant_id. Deprecated: use\ncollection.", + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "sync_mode": { - "description": "SyncMode is the per-resource update strategy for taps that support one\n(today: attio objects/lists). \"rescan\" (default) re-reads the full set\nevery sync — the only way edits are seen on APIs with no updated_at.\n\"new_only\" bounds each scan to the sync window and stops paging at its\nfloor — cheap, and an explicit opt-in to not seeing edits until\nwebhooks land. Stored in the resource's filters and carried to the tap\non every window.", + "description": "How this resource picks up changes, for providers that support a choice (currently Attio objects and lists). `rescan` (default) re-reads everything each sync, so edits are seen. `new_only` reads only new records each sync and does not pick up edits.", "enum": [ "rescan", "new_only" @@ -4655,7 +3603,7 @@ }, "tenant_id": { "deprecated": true, - "description": "Optional per-resource tenant override (for \"route specific resources to\ndifferent tenants\"). Empty means the resource inherits the connector\ntenant. Deprecated: use database.", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -4669,7 +3617,7 @@ "handler.responseMeta": { "properties": { "api_version": { - "description": "APIVersion echoes the version of the API that served the request (PRO-1209),\nsourced from reqmeta.APIVersion — the same value carried by OpenAPI\ninfo.version and /health — so a client always knows which API version\nproduced a response. Always present (no omitempty).", + "description": "Version of the API that served the request, for example `2.0.1`. Always present.", "type": "string" }, "collection": { @@ -4683,7 +3631,7 @@ "type": "string" }, "deprecation": { - "description": "Deprecation lists any migration nudges that apply to this request — the\ncaller used a legacy /tenants route, a legacy tenant_id/sub_tenant_id field,\nor the deprecated sub_tenant_ids selector. It is a non-breaking signal (the\nstatus code is unchanged); omitempty keeps it absent for fully-migrated\nrequests. A list so independent deprecations coexist without clobbering.", + "description": "Migration notices for this request, present only when it used a deprecated route, field or selector. The status code is unaffected.", "items": { "$ref": "#/components/schemas/handler.deprecationNotice" }, @@ -4707,12 +3655,14 @@ }, "sub_tenant_id": { "deprecated": true, + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -4720,175 +3670,42 @@ }, "type": "object" }, - "handler.supabaseWebhookAck": { - "properties": { - "id": { - "description": "Unique identifier for this resource.", - "example": "HydraDoc1234", - "type": "string" - }, - "status": { - "description": "Current lifecycle or processing state.", - "example": "completed", - "type": "string" - }, - "table": { - "type": "string" - } - }, - "type": "object" - }, "handler.tableConfigEntry": { "properties": { "change_history": { + "description": "Read changes from BigQuery's own change history instead of a column: `appends` (new rows only) or `changes` (inserts, updates and deletes). Set exactly one of `replication_key` or `change_history`.", "type": "string" }, "replication_key": { + "description": "An orderable last-modified column (for example `updated_at`) used to find changed rows. Set exactly one of `replication_key` or `change_history`.", "type": "string" }, "table": { + "description": "Table to configure, as its resource id from Discover (`dataset.table`). One entry per table.", "type": "string" } }, "type": "object" }, - "handler.vaultCredentialEntry": { - "properties": { - "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", - "example": "team_docs", - "type": "string" - }, - "connector_id": { - "description": "Connector this resource belongs to.", - "example": "conn_abc123", - "type": "string" - }, - "credential_id": { - "type": "string" - }, - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "fields": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "label": { - "type": "string" - }, - "provider": { - "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", - "example": "slack", - "type": "string" - } - }, - "type": "object" - }, - "handler.vaultCredentialListResponse": { - "properties": { - "count": { - "description": "Total number of items returned.", - "example": 12, - "type": "integer" - }, - "credentials": { - "description": "Provider-specific credentials (typically `{\"api_token\": \"...\"}` or `{\"access_token\": \"...\"}`).", - "example": { - "api_token": "xoxb-..." - }, - "items": { - "$ref": "#/components/schemas/handler.vaultCredentialEntry" - }, - "type": "array", - "uniqueItems": false - }, - "unavailable_count": { - "example": 1, - "type": "integer" - } - }, - "type": "object" - }, - "handler.vaultCredentialRevealReq": { - "properties": { - "field": { - "type": "string" - } - }, - "required": [ - "field" - ], - "type": "object" - }, - "handler.vaultCredentialRevealResponse": { - "properties": { - "credential_id": { - "type": "string" - }, - "field": { - "type": "string" - }, - "provider": { - "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", - "example": "slack", - "type": "string" - }, - "value": {} - }, - "type": "object" - }, - "handler.vaultCredentialUpdateReq": { - "properties": { - "credentials": { - "additionalProperties": {}, - "description": "Provider-specific credentials (typically `{\"api_token\": \"...\"}` or `{\"access_token\": \"...\"}`).", - "example": { - "api_token": "xoxb-..." - }, - "type": "object" - } - }, - "required": [ - "credentials" - ], - "type": "object" - }, - "handler.vaultCredentialUpdateResponse": { - "properties": { - "credential_id": { - "type": "string" - }, - "updated": { - "description": "Whether the source metadata was updated.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, "ingestion.GraphEntity": { "properties": { "identifier": { + "description": "Optional external id, such as an email or URL, for display only. At most 256 characters.", "example": "Acme Corp", "type": "string" }, "name": { - "description": "Human-readable label for this resource.", + "description": "Entity name. Required; at most 256 characters.", "example": "general", "type": "string" }, "namespace": { - "description": "Namespace grouping for the entity (e.g. `organization`, `person`).", + "description": "Logical grouping for the entity, for example `employees`. Stored as supplied; at most 256 characters.", "example": "organization", "type": "string" }, "type": { + "description": "Entity type, for example `PERSON` or `POLICY`. Stored as supplied; at most 256 characters.", "example": "knowledge", "type": "string" } @@ -4901,9 +3718,11 @@ "additionalProperties": { "$ref": "#/components/schemas/ingestion.GraphEntity" }, + "description": "Entities keyed by a handle of your choice (at most 256 characters) that `relations` refer to; the handle is not stored. Must not be empty; at most 5,000 entities.", "type": "object" }, "relations": { + "description": "Relations between entity handles. Must not be empty; at most 10,000 relations and 500 per entity.", "example": [ { "context": "Ada joined Acme Corp in 2024 as a staff engineer.", @@ -4922,21 +3741,24 @@ "ingestion.GraphRelation": { "properties": { "context": { - "description": "Verbatim passage from the source that evidences the relationship.", + "description": "Optional sentence supporting the relation. At most 2,000 characters.", "example": "Ada joined Acme Corp in 2024 as a staff engineer.", "type": "string" }, "predicate": { + "description": "Relationship label, any plain string. Required; at most 256 characters.", "type": "string" }, "source": { + "description": "Handle of the source entity; must be a key in `entities`.", "type": "string" }, "target": { + "description": "Handle of the target entity; must be a key in `entities`.", "type": "string" }, "temporal_details": { - "description": "Temporal context extracted alongside the relationship (e.g. `since 2024`). Serialized as null rather than omitted.", + "description": "Optional timing for the relation, for example `since 2021`. At most 256 characters.", "example": "since 2024", "type": "string" } @@ -4944,7 +3766,7 @@ "type": "object" }, "ingestion.SourceStatus": { - "description": "Status is the item's initial lifecycle state. Both modes share this\nvocabulary — memory mode reuses the same values.", + "description": "Initial state of an ingested context.", "enum": [ "queued", "processing", @@ -4962,7 +3784,7 @@ "ingestion.V2BatchProcessingStatus": { "properties": { "statuses": { - "description": "Per-source indexing status results.", + "description": "One status per requested ID.", "example": [ { "error_code": "", @@ -4985,7 +3807,7 @@ "ingestion.V2IngestResponse": { "properties": { "failed_count": { - "description": "Number of uploaded files that failed to queue.", + "description": "Number of contexts that could not be queued.", "example": 0, "type": "integer" }, @@ -4995,7 +3817,7 @@ "type": "string" }, "results": { - "description": "Per-item results.", + "description": "One result per context, in request order.", "example": [ { "error": "", @@ -5015,13 +3837,13 @@ }, "success": { "deprecated": true, - "description": "Deprecated for API clients: whether the REQUEST was accepted is the HTTP\nstatus code (202) or equivalently the envelope's top-level `success`.\nWhether each SOURCE ingested is per-item — read results[].status and\nresults[].error, then poll GET /context/status, since a 202 only means\nqueued. This flag answers neither question independently: it always\nmirrors the envelope. Still emitted unchanged for existing clients\n(PRO-1208).", + "description": "Deprecated: check the HTTP status (`202`), then `results[].status` per context. Always equals the envelope's top-level `success`.", "example": true, "type": "boolean", "x-deprecated": "true" }, "success_count": { - "description": "Number of files successfully queued for processing.", + "description": "Number of contexts queued.", "example": 2, "type": "integer" } @@ -5031,44 +3853,48 @@ "ingestion.V2IngestResultItem": { "properties": { "error": { - "description": "Error is the failure message for this item, null on success. Both modes.", + "description": "Why this context failed, or `null` on success.", "example": "", "type": "string" }, "error_code": { - "description": "ErrorCode is the machine-readable failure classification, null on success.\nBoth modes; always null on the memory path, which produces no per-item code.", + "description": "Machine-readable failure code, or `null` on success. Always `null` for contexts sent in `context`.", "type": "string" }, "filename": { - "description": "Filename is the original filename as submitted. type=knowledge only.", + "description": "Name of the uploaded file. Only returned for the deprecated `documents` upload.", "example": "policy.pdf", "type": "string" }, "id": { - "description": "ID is the source identifier assigned to this item. Both modes.", + "description": "The context's ID: the `context_id` you sent, or the one generated. Pass it to `GET /context/status`.", "example": "HydraDoc1234", "type": "string" }, "infer": { - "description": "Infer reports whether the memory was queued for inference. type=memory only.", + "description": "Whether the context was queued for enrichment.", "example": true, "type": "boolean" }, "relations_created": { - "description": "RelationsCreated is the number of graph relations extracted from this file.\ntype=knowledge only, and only for items that carried a `relations` payload.", + "description": "Number of forceful relations created for this entry. Only returned by the deprecated `documents` and `app_knowledge` fields, for entries that declared relations.", "example": 5, "type": "integer" }, "relations_error": { - "description": "RelationsError is the relation-extraction failure message, if any.\ntype=knowledge only.", + "description": "Why this entry's forceful relations could not be created. The entry is still queued. Only returned by the deprecated `documents` and `app_knowledge` fields.", "type": "string" }, "status": { - "$ref": "#/components/schemas/ingestion.SourceStatus", - "description": "Current lifecycle or processing state." + "allOf": [ + { + "$ref": "#/components/schemas/ingestion.SourceStatus" + } + ], + "description": "`queued` when the context was accepted, `failed` when it was not. A failed context does not stop the others." }, "title": { - "description": "Title is the memory's title. type=memory only.", + "description": "The context's `title`, when one was sent.", "example": "Project Phoenix Overview", "type": "string" } @@ -5078,33 +3904,33 @@ "ingestion.V2ProcessingStatus": { "properties": { "error_code": { - "description": "Machine-readable code for the indexing failure, empty string on success.", + "description": "Machine-readable reason when `indexing_status` is `errored`, for example `FILE_NOT_FOUND` for an ID that does not exist; empty string otherwise.", "example": "", "type": "string" }, "error_message": { - "description": "Human-readable description of the indexing failure, empty string on success.", + "description": "Human-readable explanation of `error_code`; empty string when there is none.", "example": "", "type": "string" }, "id": { - "description": "Unique identifier for this resource.", + "description": "The context ID you asked about.", "example": "HydraDoc1234", "type": "string" }, "indexing_status": { - "description": "Current processing state: `queued`, `processing`, `completed`, or `failed`.", + "description": "Processing state: `queued`, `processing`, `graph_creation`, `completed` or `errored`. A context is searchable from `graph_creation` on; `completed` and `errored` are terminal.", "example": "completed", "type": "string" }, "message": { - "description": "Human-readable status description.", + "description": "Result of the lookup, not of processing, for example `ID not found`. Read `indexing_status` for the context's state.", "example": "Source processed successfully.", "type": "string" }, "success": { "deprecated": true, - "description": "Deprecated for API clients: this reads like a per-source outcome but is\na constant echo of the envelope's `success` — it is true even for a\nsource that failed indexing. For the state of THIS source read\nindexing_status (and error_code/error_message when it is errored); for\nwhether the request itself succeeded read the HTTP status code or the\nenvelope's top-level `success`. Still emitted unchanged for existing\nclients (PRO-1208).", + "description": "Deprecated: read `indexing_status` and `error_code`. `false` when `indexing_status` is `errored`, otherwise `true`.", "example": true, "type": "boolean", "x-deprecated": "true" @@ -5116,7 +3942,7 @@ "properties": { "additional_metadata": { "additionalProperties": {}, - "description": "Filters /context/list by document/additional metadata. Example: {\"author\": \"ada\"}.", + "description": "Match on the context's `custom_attributes` (`additional_metadata` on this endpoint). `document_metadata` is accepted as an older name.", "example": { "author": "ada" }, @@ -5124,7 +3950,7 @@ }, "metadata": { "additionalProperties": {}, - "description": "Filters /context/list by tenant/source metadata. Example: {\"department\": \"finance\"}.", + "description": "Match on the context's `attributes` (`metadata` on this endpoint). `tenant_metadata` is accepted as an older name.", "example": { "department": "finance" }, @@ -5132,7 +3958,7 @@ }, "source_fields": { "additionalProperties": {}, - "description": "SourceFields filters by well-known source fields: title, type,\ndescription, url, timestamp, and the app-source keys app_provider,\napp_kind, app_external_id, app_parent_id.\n\napp_external_id and app_parent_id are provider-scoped: a Jira issue key\nand a Linear id can collide, so pair either with app_provider in the\nsame filter to identify one object. Without it a match may span\nproviders that reuse the same external id.", + "description": "Match on built-in fields: `title` (case-insensitive prefix), `type`, `description`, `url`, `timestamp`, `app_provider`, `app_kind`, `app_external_id`, `app_parent_id`. Pair `app_external_id` or `app_parent_id` with `app_provider`; they are only unique per provider.", "type": "object" } }, @@ -5141,7 +3967,7 @@ "list.V2ListContentRequest": { "properties": { "acl": { - "description": "ACL: see ListContentRequest.ACL (PRO-1684 document ACLs).", + "description": "Principals to answer as: only context they may see is listed. Omit it for no access scoping.", "items": { "type": "string" }, @@ -5149,17 +3975,22 @@ "uniqueItems": false }, "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "description": "Collection to list. Omit it to use the database's default collection.", "example": "team_docs", "type": "string" }, "database": { - "description": "Database/Collection are the canonical v2 names; TenantID/SubTenantID are\ntheir deprecated aliases (reconciled here in UnmarshalJSON and centrally by\nthe TenantAliases middleware).", + "description": "Database to list. Required. Formerly `tenant_id`; the alias is still accepted.", "example": "acme_corp", "type": "string" }, "filters": { - "$ref": "#/components/schemas/list.ContentFilter", + "allOf": [ + { + "$ref": "#/components/schemas/list.ContentFilter" + } + ], + "description": "Exact-match filters on `metadata`, `additional_metadata` and built-in `source_fields`. All pairs must match.", "example": { "additional_metadata": { "author": "ada" @@ -5170,12 +4001,12 @@ } }, "group_threads": { - "description": "GroupThreads (type=knowledge only) folds each ticket's/thread root's\ndiscussion (comment and message app sources carrying an app_parent_id)\nunder the parent row as `comments`, newest first, instead of listing them\nas separate top-level rows. Off by default: the flat shape is the\nexisting contract.", + "description": "Nest each thread's comments and replies under their parent row as `comments`, newest first, instead of listing them as separate rows. Default `false`.", "example": true, "type": "boolean" }, "ids": { - "description": "When provided, only items with these IDs are returned. Pagination and filters still apply.", + "description": "List only these context IDs, at most 100. Pagination and `filters` still apply.", "example": [ "HydraDoc1234", "HydraDoc4567" @@ -5187,7 +4018,7 @@ "uniqueItems": false }, "include_fields": { - "description": "Field projection — only the listed fields plus id, database, collection are returned. Only applies to type=knowledge.", + "description": "Return only these fields, plus `id`, `database` and `collection`. Allowed: `title`, `type`, `description`, `note`, `timestamp`, `metadata`, `additional_metadata`, `relations`, `context_category`.", "example": [ "id", "title", @@ -5200,32 +4031,32 @@ "uniqueItems": false }, "page": { - "description": "Current page number (1-indexed).", + "description": "Page number, starting at 1. Default 1.", "example": 1, "type": "integer" }, "page_size": { - "description": "Number of items per page.", + "description": "Rows per page, from 1 to 100. Default 50.", "example": 50, "type": "integer" }, "sub_tenant_id": { "deprecated": true, - "description": "deprecated: use collection", + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" }, "type": { "deprecated": true, - "description": "Deprecated: kept for split databases.\nType names the corpus: knowledge (default) or memory.", + "description": "Deprecated: omit it. Kept for older databases, where `memory` lists the memory corpus instead of `knowledge`.", "enum": [ "knowledge", "memory", @@ -5257,7 +4088,7 @@ } }, "sources": { - "description": "Sources carries the rows when type=knowledge (the default).", + "description": "The listed context, one row per context.", "example": [ { "additional_metadata": { @@ -5292,18 +4123,18 @@ }, "success": { "deprecated": true, - "description": "Deprecated for API clients: to decide whether the request succeeded,\ncheck the HTTP status code — 2xx is success — or equivalently the\nenvelope's top-level `success`. This nested copy always carries the same\nvalue and never carries independent information. Still emitted unchanged\nfor existing clients (PRO-1208).", + "description": "Deprecated: check the HTTP status instead. Always equals the envelope's top-level `success`.", "example": true, "type": "boolean", "x-deprecated": "true" }, "total": { - "description": "Total is the total number of matching rows across all pages.", + "description": "Number of matching rows across all pages.", "example": 128, "type": "integer" }, "user_memories": { - "description": "UserMemories carries the rows when type=memory. Same item shape as Sources\nexcept each row is keyed by memory_id rather than id.", + "description": "Rows when the deprecated `type: \"memory\"` is sent. Same fields as `sources`, keyed by `memory_id`.", "example": [ { "additional_metadata": { @@ -5343,7 +4174,7 @@ "properties": { "additional_metadata": { "additionalProperties": {}, - "description": "AdditionalMetadata is the caller-supplied per-document metadata (stored as\ndocument_metadata).", + "description": "The context's `custom_attributes`, under their older name.", "example": { "author": "ada", "doc_version": 3 @@ -5351,35 +4182,38 @@ "type": "object" }, "app_external_id": { - "description": "Provider-assigned identifier for this source (e.g. Slack channel ID).", + "description": "Provider-assigned identifier, for example a Slack channel ID.", "example": "C0123456789", "type": "string" }, "app_kind": { - "description": "App integration category, populated for connector-synced sources.", + "description": "Connector object category.", "example": "slack", "type": "string" }, "app_parent_id": { - "description": "AppParentID is the provider external id of this source's conversational\nparent (a Jira comment carries its issue key, a Slack reply its thread\nroot), and AppThreadID the discussion grouping key. Mirrored from the\ningestion pipeline; absent for sources without a parent/thread.", + "description": "Provider ID of the parent in a discussion, for example a Jira comment's issue key or a Slack reply's thread root.", "type": "string" }, "app_provider": { - "description": "App* carry connector provenance, mirrored onto the source document by the\ningestion pipeline. Null for sources that were not connector-ingested.", + "description": "Connector the context came from, for example `slack` or `github`. Absent for context that did not come from a connector.", "example": "slack", "type": "string" }, - "app_relations": {}, + "app_relations": { + "description": "Relations derived by the connector." + }, "app_thread_id": { + "description": "Discussion grouping key shared by a thread root and its replies or comments.", "type": "string" }, "collection": { - "description": "Collection is the canonical name for the sub-scope this row was listed\nfrom. Empty string when the row lives in the database's default collection.", + "description": "Collection the row was listed from; empty string for the database's default collection. Always present.", "example": "team_docs", "type": "string" }, "comments": { - "description": "Comments is the group_threads discussion: the source's comment/message\nchildren as full sibling rows, newest first, capped per parent with\nCommentsTruncated marking an overflow. Present (possibly empty) on every\nrow of a group_threads response; absent otherwise.", + "description": "With `group_threads`, the row's comments and replies as full rows, newest first, capped per parent.", "items": { "additionalProperties": {}, "type": "object" @@ -5388,31 +4222,32 @@ "uniqueItems": false }, "comments_truncated": { + "description": "With `group_threads`, `true` when `comments` hit the per-parent cap and more exist.", "example": true, "type": "boolean" }, "context_category": { - "description": "ContextCategory is the context category the caller pinned on ingest:\none of user_preference, business_knowledge or decision_trace, written\nonto the source row by the ingestion pipeline (PRO-1618). Absent when no\ncategory was pinned, on rows ingested before the category existed, and\non every split-database row, which carries no category.", + "description": "The `context_category` set at ingest. Absent when none was set.", "type": "string" }, "database": { - "description": "Database is the canonical name for the scope this row was listed from.", + "description": "Database the row was listed from. Always present.", "example": "acme_corp", "type": "string" }, "description": { - "description": "Human-readable description of the source.", + "description": "Human-readable description of the context.", "example": "Internal overview of the Project Phoenix rollout.", "type": "string" }, "memory_id": { - "description": "MemoryID is the memory identifier — the type=memory spelling of ID, and\npresent on exactly the same terms.", + "description": "The context's ID. Always present.", "example": "memory_1234", "type": "string" }, "metadata": { "additionalProperties": {}, - "description": "Metadata is the caller-supplied source metadata (stored as tenant_metadata).", + "description": "The context's `attributes`, under their older name.", "example": { "department": "finance", "priority": 7 @@ -5420,38 +4255,39 @@ "type": "object" }, "note": { + "description": "Free-form note attached to the context.", "example": "Superseded by the Q3 rollout plan.", "type": "string" }, "relations": { - "description": "Relations/AppRelations are passthrough graph subtrees, returned only when\nrequested via include_fields. Their internal source_id/source_ids keys are\nrenamed to id/ids on the way out; the rest of the subtree is unconstrained." + "description": "Relations attached to the context. Returned only when requested with `include_fields`." }, "sub_tenant_id": { "deprecated": true, - "description": "SubTenantID is the deprecated spelling of Collection, carrying an identical value.", + "description": "Deprecated: use `collection`, which carries the same value.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "tenant_id": { "deprecated": true, - "description": "TenantID is the deprecated spelling of Database, carrying an identical value.", + "description": "Deprecated: use `database`, which carries the same value.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" }, "timestamp": { - "description": "RFC3339 timestamp associated with this item.", + "description": "RFC3339 timestamp associated with the context.", "example": "2026-07-02T10:00:00Z", "type": "string" }, "title": { - "description": "Title or name of the source.", + "description": "Title of the context.", "example": "Project Phoenix Overview", "type": "string" }, "type": { - "description": "Type is the source kind, e.g. \"knowledge\" or \"memory\".", + "description": "Kind of source the context came from.", "example": "knowledge", "type": "string" } @@ -5469,7 +4305,7 @@ "properties": { "additional_metadata": { "additionalProperties": {}, - "description": "AdditionalMetadata is the caller-supplied per-document metadata (stored as\ndocument_metadata).", + "description": "The context's `custom_attributes`, under their older name.", "example": { "author": "ada", "doc_version": 3 @@ -5477,38 +4313,38 @@ "type": "object" }, "app_external_id": { - "description": "Provider-assigned identifier for this source (e.g. Slack channel ID).", + "description": "Provider-assigned identifier, for example a Slack channel ID.", "example": "C0123456789", "type": "string" }, "app_kind": { - "description": "App integration category, populated for connector-synced sources.", + "description": "Connector object category.", "example": "slack", "type": "string" }, "app_parent_id": { - "description": "AppParentID is the provider external id of this source's conversational\nparent (a Jira comment carries its issue key, a Slack reply its thread\nroot), and AppThreadID the discussion grouping key. Mirrored from the\ningestion pipeline; absent for sources without a parent/thread.", + "description": "Provider ID of the parent in a discussion, for example a Jira comment's issue key or a Slack reply's thread root.", "type": "string" }, "app_provider": { - "description": "App* carry connector provenance, mirrored onto the source document by the\ningestion pipeline. Null for sources that were not connector-ingested.", + "description": "Connector the context came from, for example `slack` or `github`. Absent for context that did not come from a connector.", "example": "slack", "type": "string" }, "app_relations": { - "description": "Connector-derived relations for this source. Present on connector-ingested rows." + "description": "Relations derived by the connector." }, "app_thread_id": { - "description": "Discussion grouping key shared by a thread root and its replies/comments. Absent for unthreaded sources.", + "description": "Discussion grouping key shared by a thread root and its replies or comments.", "type": "string" }, "collection": { - "description": "Collection is the canonical name for the sub-scope this row was listed\nfrom. Empty string when the row lives in the database's default collection.", + "description": "Collection the row was listed from; empty string for the database's default collection. Always present.", "example": "team_docs", "type": "string" }, "comments": { - "description": "Comments is the group_threads discussion: the source's comment/message\nchildren as full sibling rows, newest first, capped per parent with\nCommentsTruncated marking an overflow. Present (possibly empty) on every\nrow of a group_threads response; absent otherwise.", + "description": "With `group_threads`, the row's comments and replies as full rows, newest first, capped per parent.", "items": { "additionalProperties": {}, "type": "object" @@ -5517,32 +4353,32 @@ "uniqueItems": false }, "comments_truncated": { - "description": "True when the inline `comments` array hit the per-parent cap and more children exist. Fetch them via `filters.additional_metadata` on the parent's external ID.", + "description": "With `group_threads`, `true` when `comments` hit the per-parent cap and more exist.", "example": true, "type": "boolean" }, "context_category": { - "description": "ContextCategory is the context category the caller pinned on ingest:\none of user_preference, business_knowledge or decision_trace, written\nonto the source row by the ingestion pipeline (PRO-1618). Absent when no\ncategory was pinned, on rows ingested before the category existed, and\non every split-database row, which carries no category.", + "description": "The `context_category` set at ingest. Absent when none was set.", "type": "string" }, "database": { - "description": "Database is the canonical name for the scope this row was listed from.", + "description": "Database the row was listed from. Always present.", "example": "acme_corp", "type": "string" }, "description": { - "description": "Human-readable description of the source.", + "description": "Human-readable description of the context.", "example": "Internal overview of the Project Phoenix rollout.", "type": "string" }, "id": { - "description": "ID is the source identifier. Always present: buildProjection pins\nsource.id as an identity field on every projection path.", + "description": "The context's ID. Always present.", "example": "HydraDoc1234", "type": "string" }, "metadata": { "additionalProperties": {}, - "description": "Metadata is the caller-supplied source metadata (stored as tenant_metadata).", + "description": "The context's `attributes`, under their older name.", "example": { "department": "finance", "priority": 7 @@ -5550,39 +4386,39 @@ "type": "object" }, "note": { - "description": "Free-form note attached to the source.", + "description": "Free-form note attached to the context.", "example": "Superseded by the Q3 rollout plan.", "type": "string" }, "relations": { - "description": "Relations/AppRelations are passthrough graph subtrees, returned only when\nrequested via include_fields. Their internal source_id/source_ids keys are\nrenamed to id/ids on the way out; the rest of the subtree is unconstrained." + "description": "Relations attached to the context. Returned only when requested with `include_fields`." }, "sub_tenant_id": { "deprecated": true, - "description": "SubTenantID is the deprecated spelling of Collection, carrying an identical value.", + "description": "Deprecated: use `collection`, which carries the same value.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" }, "tenant_id": { "deprecated": true, - "description": "TenantID is the deprecated spelling of Database, carrying an identical value.", + "description": "Deprecated: use `database`, which carries the same value.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" }, "timestamp": { - "description": "RFC3339 timestamp associated with this item.", + "description": "RFC3339 timestamp associated with the context.", "example": "2026-07-02T10:00:00Z", "type": "string" }, "title": { - "description": "Title or name of the source.", + "description": "Title of the context.", "example": "Project Phoenix Overview", "type": "string" }, "type": { - "description": "Type is the source kind, e.g. \"knowledge\" or \"memory\".", + "description": "Kind of source the context came from.", "example": "knowledge", "type": "string" } @@ -5596,52 +4432,115 @@ ], "type": "object" }, - "memories.ConversationTurn": { + "memories.ContextIngestRequest": { "properties": { - "content": { - "description": "Extracted text content of the source document.", - "example": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", + "collection": { + "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "example": "team_docs", "type": "string" }, - "role": { - "type": "string" - } - }, - "type": "object" - }, - "memories.ForcefulRelations": { - "description": "ForcefulRelations are the contexts the caller says this one relates to,\nby context_id, with optional properties stored on each edge. They are\nfollowed at query time (follow_forceful_relations) and returned as\n`forceful_relations[]`. Any item may declare them; on the split surface\nonly a knowledge item could.", - "properties": { - "context_ids": { + "context": { + "description": "The contexts to ingest, 1 to 100 per request. Each is exactly one of `text` or `conversation`. At most 1 MiB of text per context and 8 MiB of text per request.", + "example": "Ada joined Acme Corp in 2024 as a staff engineer.", "items": { - "type": "string" + "$ref": "#/components/schemas/memories.IngestItem" }, "type": "array", "uniqueItems": false }, - "properties": { - "additionalProperties": {}, - "type": "object" - } - }, - "type": "object" - }, - "memories.IngestItem": { - "properties": { - "acl": { - "description": "ACL is the item's access-control list (PRO-1684), the same contract as\nan app_knowledge item's `acl` on a split database: bare emails,\nuser_email:/group:/domain: principals, or the __public__/__private__\nsentinels. Omitted (nil) leaves the context unrestricted; an explicitly\nempty list stores __private__. Normalised here, all-or-nothing, so a\nmalformed principal is a 400 on the request rather than a silently\nmis-scoped context. Enforced by every read that takes `acl`.", - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", + "type": "string" }, - "attributes": { - "additionalProperties": {}, + "enrich": { + "description": "Default `enrich` for every context that does not set its own. Default `true`.", + "example": true, + "type": "boolean" + }, + "graph_payload": { + "additionalProperties": { + "$ref": "#/components/schemas/ingestion.GraphPayload" + }, + "description": "Your own graph for contexts in this request, keyed by `context_id`. HydraDB uses it instead of extracting a graph from that context, which is still chunked and embedded. A key that matches no `context_id` in the request is a `400`.", + "type": "object" + }, + "instructions": { + "description": "Default enrichment instructions for every context that sets none. At most 4,000 characters.", + "type": "string" + }, + "sub_tenant_id": { + "deprecated": true, + "description": "Deprecated: use `collection`.", + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + }, + "tenant_id": { + "deprecated": true, + "description": "Deprecated: use `database`.", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + }, + "upsert": { + "description": "Default `upsert` for every context that does not set its own. Default `true`.", + "example": "true", + "type": "boolean" + } + }, + "type": "object" + }, + "memories.ConversationTurn": { + "properties": { + "content": { + "description": "Text of the turn. Must not be empty.", + "example": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", + "type": "string" + }, + "role": { + "description": "Who spoke the turn: `user`, `assistant` or `system`. `system` turns are never stored as facts; they become the context's instructions when none are set.", + "type": "string" + } + }, + "type": "object" + }, + "memories.ForcefulRelations": { + "description": "Contexts this one is declared related to, by `context_id`, with optional properties stored on each link.", + "properties": { + "context_ids": { + "description": "The `context_id`s this context relates to. Each follows the same rules as `context_id`.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "properties": { + "additionalProperties": {}, + "description": "Optional properties stored on every relation this context declares: a flat map of string, number or boolean values, at most 1 KiB as compact JSON.", + "type": "object" + } + }, + "type": "object" + }, + "memories.IngestItem": { + "properties": { + "acl": { + "description": "Principals allowed to retrieve the context: bare emails or `user_email:`, `group:` and `domain:` principals, or `__public__`. Omit it for unrestricted; send `[]` for nobody. A malformed principal rejects the whole request with `400`.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "attributes": { + "additionalProperties": {}, + "description": "Filterable fields declared in the database's metadata schema. At most 16 KiB as compact JSON. Filter on them with `attributes` on `/query`.", "type": "object" }, "context_category": { - "description": "ContextCategory files this context under one of the three buckets\n(PRO-1618). Omitted or \"auto\" leaves it to HydraDB; naming a bucket pins\nit and inference will not overwrite it. See\ndomain/ingestion/context_category.go.", + "description": "Label for what the context holds: `user_preference`, `business_knowledge` or `decision_trace`. `auto`, the default, sets no label. Any other value is a `400`.", "enum": [ "auto", "user_preference", @@ -5651,10 +4550,11 @@ "type": "string" }, "context_id": { + "description": "Your id for the context, and the upsert key. Generated when omitted. At most 100 bytes; must not contain a comma (`,`) or start with `att_` or `cmt_`.", "type": "string" }, "conversation": { - "description": "Conversation is the shape a developer already builds for OpenAI or\nAnthropic: `[{role, content}]`.", + "description": "Turns of `{role, content}`, the shape chat model APIs use. Send exactly one of `text` or `conversation`. Needs at least one `user` or `assistant` turn; the speaker is the context's `user_name`.", "example": [ { "content": "# Q4 Report\n\nRevenue grew 23% quarter over quarter." @@ -5668,1343 +4568,1057 @@ }, "custom_attributes": { "additionalProperties": {}, + "description": "Free-form fields stored with the context. Not filterable. At most 1 KiB as compact JSON.", "type": "object" }, "enrich": { + "description": "Extract entities, relations and preferences from this context into the graph. Defaults to the request's `enrich`, else `true`.", "example": true, "type": "boolean" }, "forceful_relations": { - "$ref": "#/components/schemas/memories.ForcefulRelations" + "allOf": [ + { + "$ref": "#/components/schemas/memories.ForcefulRelations" + } + ], + "description": "Other contexts you declare this one relates to. Followed on `/query` with `follow_forceful_relations` and returned in `forceful_relations`." }, "happened_at": { + "description": "The date the context is about, as `YYYY-MM-DD`. A timestamp is a `400`. The time HydraDB received the context is recorded separately.", "type": "string" }, "instructions": { - "description": "Instructions steer enrichment for this item. The request-level value is\nthe default when an item names none.", + "description": "Steer enrichment for this context. At most 4,000 characters. Defaults to the request's `instructions`.", "type": "string" }, "text": { + "description": "Plain text. Send exactly one of `text` or `conversation`.", "type": "string" }, "title": { - "description": "Title names the context. It becomes the context's document title, and it\nis what distinguishes two items whose text is identical: the document id\nis generated from the title, so without one they collide.", + "description": "Readable name for the context. Trimmed, then at most 1,024 bytes of UTF-8. Two contexts with identical text and no `context_id` are told apart by their title.", "example": "Project Phoenix Overview", "type": "string" }, "upsert": { - "description": "Upsert decides, for THIS item, whether an existing context with the same\ncontext_id is replaced. The request-level value is the default. This is\nwhat lets one call replace some contexts and append others.", + "description": "Replace an existing context with the same `context_id`. Defaults to the request's `upsert`, else `true`.", "example": "true", "type": "boolean" }, "user_name": { - "description": "UserName is the speaker identity for the item, on both shapes: a text\nitem is what that person said, a conversation's user turns are theirs.\nEmpty ends up as \"User\", matching the split path, so the pipeline is\nnever handed a blank.", + "description": "The speaker: the author of a `text` context, or the person in a conversation's `user` turns. Default `User`.", "type": "string" } }, "type": "object" }, - "memories.UnifiedIngestRequest": { + "search.MetadataFilters": { + "additionalProperties": {}, + "description": "Deprecated: use `attributes`.", + "example": { + "active": true, + "additional_metadata": { + "author": "ada" + }, + "department": "finance", + "priority": 7, + "tags": [ + "alpha", + "beta" + ] + }, + "type": "object" + }, + "search.Operator": { + "enum": [ + "or", + "and", + "phrase" + ], + "type": "string", + "x-enum-varnames": [ + "OperatorOr", + "OperatorAnd", + "OperatorPhrase" + ] + }, + "search.QueryBy": { + "enum": [ + "hybrid", + "text" + ], + "type": "string", + "x-enum-varnames": [ + "QueryByHybrid", + "QueryByText" + ] + }, + "search.QueryChunk": { "properties": { - "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", - "example": "team_docs", + "chunk_id": { + "description": "The chunk's id. Every graph hop names the chunk it was extracted from by this id.", "type": "string" }, - "context": { - "description": "Context is the list of contexts to ingest.", - "example": "Ada joined Acme Corp in 2024 as a staff engineer.", - "items": { - "$ref": "#/components/schemas/memories.IngestItem" - }, - "type": "array", - "uniqueItems": false - }, - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", + "content": { + "description": "The chunk's own text. Enrichment is not concatenated into it.", "type": "string" }, - "enrich": { - "example": true, - "type": "boolean" + "context_id": { + "description": "The id of the context (source) the chunk belongs to.", + "type": "string" }, - "graph_payload": { - "additionalProperties": { - "$ref": "#/components/schemas/ingestion.GraphPayload" - }, - "description": "GraphPayload is the bring-your-own-graph map, keyed by context_id. Every\nkey must name an item in this request, so a typo cannot silently drop a\ngraph.", - "type": "object" + "enrichment": { + "description": "What enrichment produced for the chunk, kept apart from content. Absent when nothing was produced.", + "type": "string" }, - "instructions": { + "enrichment_kind": { + "description": "The context_category the author declared at ingest (user_preference, business_knowledge or decision_trace). Never inferred. Absent when none was declared.", + "enum": [ + "user_preference", + "business_knowledge", + "decision_trace" + ], "type": "string" }, - "sub_tenant_id": { - "deprecated": true, - "description": "deprecated: use collection", - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" + "received_at": { + "description": "When the context this chunk belongs to was received (RFC 3339). This is the ingest time, not the caller's happened_at, which is not echoed here. Omitted when the store holds no receipt time for the row (older rows); it is never sent empty.", + "type": "string" }, - "tenant_id": { - "deprecated": true, - "description": "deprecated: use database", - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" + "score": { + "description": "Relevance after reranking.", + "type": "number" }, - "upsert": { - "description": "Upsert, Enrich and Instructions are the request-level defaults for the\nitem-level fields of the same name: true, true and \"\" when absent.", - "example": "true", - "type": "boolean" + "temporal": { + "description": "Dated facts extracted from the chunk. Present only when the query engaged temporal reasoning.", + "items": { + "$ref": "#/components/schemas/search.QueryChunkTemporal" + }, + "type": "array" } }, + "required": [ + "chunk_id", + "context_id", + "score", + "content" + ], "type": "object" }, - "search.AliasExpansionNote": { + "search.QueryChunkTemporal": { "properties": { - "alias": { + "content": { + "description": "The fact as a sentence with its dates embedded.", "type": "string" }, - "canonical": { - "type": "string" + "end_date": { + "description": "End of the fact's window, YYYY-MM-DD, or null.", + "type": [ + "string", + "null" + ] + }, + "start_date": { + "description": "Start of the fact's window, YYYY-MM-DD, or null.", + "type": [ + "string", + "null" + ] } }, + "required": [ + "content", + "start_date", + "end_date" + ], "type": "object" }, - "search.AppSearchFusionDiagnostics": { - "description": "AppSearchFusion is the diagnostic block of the query_apps fusion\n(PRO-1882): per-chunk lane attribution and counts. Present only when\nquery_apps was on, the request was not ACL-scoped, and attributed chunks\nsurvived final filtering. Identifier maps cover only returned chunks.", + "search.QueryForcefulRelation": { "properties": { - "app_recipes": { - "additionalProperties": { - "type": "string" - }, - "description": "AppRecipes maps the chunk_uuid of each final chunk the app lane returned to\nthe recipe that produced it (exact_id, recall, dated, bm25, broad, ...),\nincluding chunks the normal lane also had, so a consensus can be\nattributed to a recipe.", - "type": "object" - }, - "chunk_origins": { - "additionalProperties": { - "type": "string" - }, - "description": "ChunkOrigins maps every returned chunk_uuid to where the fusion placed it\nfrom: \"normal\" (normal lane only), \"both\" (both lanes, normal position\nkept), \"exact_id\" (promoted from the app lane's exact-identifier\nrecipe), \"app_tail\" (appended from the app lane).", - "type": "object" - }, - "stats": { - "$ref": "#/components/schemas/search.AppSearchFusionStats", - "description": "Counts for the first or only fusion pass before postprocessing, not final response counts or totals across alias alternatives. The entire diagnostic block is omitted for ACL-scoped requests.", - "example": { - "app_chunks": 1, - "app_has_exact_ids": true, - "app_lane_empty_text": true, - "consensus": 1, - "exact_candidates": 1, - "exact_promoted": 1, - "limit": 1, - "normal_chunks": 1, - "normal_displaced": 1, - "tail_added": 1, - "tail_candidates": 1 - } + "chunk": { + "$ref": "#/components/schemas/search.QueryChunk" }, - "stats_by_pass": { - "description": "StatsByPass preserves each independent fusion's accounting when results\ncombine multiple passes, in merge order (original before alternate when\nboth have diagnostics). Counts overlap; they are not unique totals.", - "example": [ - { - "app_chunks": 1, - "app_has_exact_ids": true, - "app_lane_empty_text": true, - "consensus": 1, - "exact_candidates": 1, - "exact_promoted": 1, - "limit": 1, - "normal_chunks": 1, - "normal_displaced": 1, - "tail_added": 1, - "tail_candidates": 1 - } - ], - "items": { - "$ref": "#/components/schemas/search.AppSearchFusionStats" - }, - "type": "array", - "uniqueItems": false + "via": { + "$ref": "#/components/schemas/search.RelationVia", + "description": "The declared edge that pulled the chunk in: from is the context that declared it, to is the chunk's own context." } }, + "required": [ + "via", + "chunk" + ], "type": "object" }, - "search.AppSearchFusionStats": { - "description": "Stats describes the first (or only) fusion pass before postprocessing,\nnot final counts or a sum across alias alternatives/fan-out branches.", + "search.QueryGraphEdge": { "properties": { - "app_chunks": { - "description": "AppChunks is what the app lane returned.", - "example": 1, - "type": "integer" + "chunk_id": { + "description": "The chunk the relation was extracted from. For a chunk_relation path this is the returned chunk the path hangs under.", + "type": "string" }, - "app_has_exact_ids": { - "description": "AppHasExactIDs mirrors the app plan's exact-identifier marker.", - "example": true, - "type": "boolean" + "context": { + "description": "The sentence the relation was extracted from.", + "type": "string" }, - "app_lane_empty_text": { - "description": "AppLaneEmptyText is true when the app lane returned no chunks (sources\nor side context only).", - "example": true, - "type": "boolean" + "predicate": { + "description": "The relation between the two entities.", + "type": "string" }, - "consensus": { - "description": "Consensus counts app chunks the normal lane already had; they keep the\nnormal lane's position.", - "example": 1, - "type": "integer" + "relationship_id": { + "description": "The relation's stable id.", + "type": "string" }, - "exact_candidates": { - "description": "ExactCandidates counts app chunks the exact-identifier recipe found;\nExactPromoted is how many of them were placed above the normal lane.", - "example": 1, - "type": "integer" - }, - "exact_promoted": { - "description": "Exact-identifier chunks placed above the normal lane.", - "example": 1, - "type": "integer" - }, - "limit": { - "description": "Limit is the final chunk limit the fusion applied.", - "example": 1, - "type": "integer" - }, - "normal_chunks": { - "description": "NormalChunks is what the normal lane returned.", - "example": 1, - "type": "integer" - }, - "normal_displaced": { - "description": "NormalDisplaced counts normal-lane chunks the promoted block and the\ntail pushed past the limit.", - "example": 1, - "type": "integer" + "temporal_details": { + "description": "When the relation held, as extraction phrased it.", + "type": "string" }, - "tail_added": { - "description": "App-only chunks appended within the tail budget.", - "example": 1, - "type": "integer" + "timestamp": { + "description": "When the relation was introduced (the date of the source it was extracted from), in Unix epoch seconds, possibly fractional. Omitted when the relation has none.", + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "predicate", + "context", + "relationship_id", + "chunk_id" + ], + "type": "object" + }, + "search.QueryGraphEntity": { + "properties": { + "entity_id": { + "description": "The entity's stable id.", + "type": "string" }, - "tail_candidates": { - "description": "TailCandidates counts app-only chunks eligible for the tail; TailAdded\nis how many were appended within the tail budget.", - "example": 1, - "type": "integer" + "name": { + "description": "The entity's name.", + "type": "string" } }, + "required": [ + "entity_id", + "name" + ], "type": "object" }, - "search.ChunkInspectResult": { + "search.QueryGraphPath": { "properties": { - "chunks": { - "example": [ - { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_id": "HydraDoc1234", - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } + "origin": { + "description": "How the path was found: `query_path` (grown from the entities in the query) or `chunk_relation` (the neighbourhood of a returned chunk).", + "enum": [ + "query_path", + "chunk_relation" ], - "items": { - "$ref": "#/components/schemas/search.VectorStoreChunk" - }, - "type": "array", - "uniqueItems": false - }, - "is_truncated": { - "description": "IsTruncated reports that the source has more chunks than the limit\nreturned, so the reader knows the text they see is a prefix of the\ndocument and not the whole of it.", - "example": false, - "type": "boolean" + "type": "string" }, - "message": { - "description": "Human-readable result message.", - "example": "Success", + "path_summary": { + "description": "The path narrated as one sentence.", "type": "string" }, - "missing_chunk_ids": { - "description": "MissingChunkIDs are ids the caller asked for that have no chunk row in\neither store. An expected, documented state rather than an error: on\nstaging 61% of one Slack collection's sources had graph relations but no\nchunk_data row at all (see attributedSourceID), and the vector store is\nnot guaranteed to still hold a re-ingested source's older chunk ids.\nAlways empty for a source-scoped read, which discovers ids rather than\nbeing handed them.", + "triplets": { + "description": "The path's hops, in order.", "items": { - "type": "string" + "$ref": "#/components/schemas/search.QueryGraphTriplet" }, - "type": "array", - "uniqueItems": false - }, - "success": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" + "type": "array" } }, + "required": [ + "origin", + "triplets", + "path_summary" + ], "type": "object" }, - "search.CodeSearchRepoResult": { + "search.QueryGraphTriplet": { "properties": { - "answer": { - "type": "string" - }, - "duration_ms": { - "example": 0.5, - "type": "number" - }, - "error": { - "description": "Error message, empty string on success.", - "example": "", - "type": "string" - }, - "repo": { - "type": "string" - }, - "status": { - "description": "Current lifecycle or processing state.", - "example": "completed", - "type": "string" + "relation": { + "$ref": "#/components/schemas/search.QueryGraphEdge" }, - "truncated": { - "example": true, - "type": "boolean" + "source": { + "$ref": "#/components/schemas/search.QueryGraphEntity" }, - "unsigned": { - "example": true, - "type": "boolean" + "target": { + "$ref": "#/components/schemas/search.QueryGraphEntity" } }, + "required": [ + "source", + "relation", + "target" + ], "type": "object" }, - "search.CodeSearchResult": { - "description": "CodeSearch is the repository code-search branch's answer, when routed.", + "search.QueryRequest": { "properties": { - "decided_by": { - "description": "DecidedBy names the signal that routed the query: \"request\" (caller\nforced it), \"planner\" (is_code_query) or \"stage2\" (embedding router).", + "acl": { + "description": "Query on behalf of an identity: results are limited to context these principals may retrieve, plus public and unrestricted context. Entries are emails or principals such as `user_email:`, `group:` or `domain:`. Omit it, or send `[]` or `[\"*\"]`, for no access scoping. An unrecognized entry matches only public and unrestricted context.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "additional_context": { + "description": "Optional context string prepended to the query to improve retrieval relevance.", + "example": "The user is a senior engineer onboarding to the platform.", "type": "string" }, - "duration_ms": { - "description": "DurationMS is the wall time the branch took.", - "example": 0.5, - "type": "number" + "alpha": { + "description": "Weighting balance between dense and sparse retrieval in hybrid mode. `\"auto\"` lets HydraDB choose; a number from 0 (full BM25) to 1 (full dense) sets it explicitly." }, - "reason": { - "description": "Reason explains a non-ok status in one sentence.", + "attributes": { + "additionalProperties": {}, + "description": "Filter on the database's declared attributes with an operator query: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$exists`. Applies to chunks, forceful relations and graph paths alike, and is ANDed with `metadata_filters` when both are sent. It cannot filter `custom_attributes`.", + "type": "object" + }, + "code_search": { + "description": "Force repository code search on (`true`) or off (`false`) for this query. Omit it to let HydraDB decide. Has no effect where code search is not enabled.", + "example": true, + "type": "boolean" + }, + "collection": { + "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "example": "team_docs", "type": "string" }, - "repos": { - "description": "Repos lists each repository searched with its own status and answer.", + "collections": { + "description": "Preferred /query scope selector. Send either a list of collection IDs for equal normalized weighting, or an object mapping collection ID to a positive relative ranking weight with at most one decimal place. Do not send together with the deprecated sub_tenant_ids or sub_tenant_id.", "example": [ + "team_docs", + "engineering" + ], + "oneOf": [ { - "duration_ms": 0.5, - "error": "", - "status": "completed", - "truncated": true, - "unsigned": true + "example": [ + "finance", + "legal" + ], + "items": { + "type": "string" + }, + "maxItems": 100, + "minItems": 1, + "title": "List of collections", + "type": "array" + }, + { + "additionalProperties": { + "exclusiveMinimum": 0, + "multipleOf": 0.1, + "type": "number" + }, + "example": { + "finance": 1.5, + "legal": 0.8 + }, + "maxProperties": 100, + "minProperties": 1, + "title": "Weighted collections", + "type": "object" } ], - "items": { - "$ref": "#/components/schemas/search.CodeSearchRepoResult" - }, - "type": "array", - "uniqueItems": false + "x-preferred": true }, - "status": { - "description": "Status is \"ok\" when at least one repository answered, \"not_found\" when\nnone had an archive, \"error\"/\"timeout\" when the branch failed, or\n\"skipped\" with a Reason when it was not attempted (no repositories\nconnected, caller opted out).", - "example": "completed", - "type": "string" - } - }, - "type": "object" - }, - "search.EntityProfileView": { - "properties": { - "compiled_at": { + "database": { + "description": "The database to query. Formerly `tenant_id`, which is still accepted.", + "example": "acme_corp", "type": "string" }, - "entity_id": { - "description": "Unique identifier for this entity in the graph.", - "example": "entity_1a2b", - "type": "string" + "follow_forceful_relations": { + "description": "Whether to follow the relations the author declared at ingest (forceful_relations) and return the related contexts. Defaults to true when omitted.", + "example": true, + "type": "boolean" }, - "entity_type": { - "type": "string" + "graph_context": { + "description": "Whether to include graph context in the response. Defaults to true for /query when omitted.", + "example": true, + "type": "boolean" }, - "entries": { + "ids": { + "description": "Restrict retrieval to these `context_id`s, at most 200. A scoped query that matches nothing returns nothing rather than widening to the whole scope.", "example": [ - { - "confidence": 0.92 - } + "HydraDoc1234", + "HydraDoc4567" ], "items": { - "$ref": "#/components/schemas/search.ProfileEntry" + "type": "string" }, "type": "array", "uniqueItems": false }, - "headline": { - "type": "string" + "max_results": { + "description": "Maximum number of chunks to return.", + "example": 10, + "type": "integer" }, - "name": { - "description": "Human-readable label for this resource.", - "example": "general", - "type": "string" + "metadata_filters": { + "allOf": [ + { + "$ref": "#/components/schemas/search.MetadataFilters" + } + ], + "deprecated": true, + "description": "Deprecated: use `attributes`. Still the only filter on `custom_attributes`, nested under `additional_metadata`.", + "x-deprecated": true }, - "pending_importance": { - "example": 1, + "mode": { + "$ref": "#/components/schemas/search.RecallMode", + "example": "thinking" + }, + "num_related_chunks": { + "description": "Number of adjacent chunks to pull alongside each matched chunk for additional context.", + "example": 3, "type": "integer" }, - "perspective": { + "operator": { + "$ref": "#/components/schemas/search.Operator", + "example": "and" + }, + "profile_entity_type": { + "description": "Entity type of `profile_subject`. Defaults to `PERSON`.", "type": "string" }, - "subject": { + "profile_namespace": { + "description": "Namespace of `profile_subject`. Defaults to `users`.", "type": "string" }, - "summary": { + "profile_subject": { + "description": "Name of an entity whose compiled profile is added to `llm_prompt` under `## Profiles`. Never changes which chunks are returned. Omit it for no requested profile. Has no effect where entity profiles are not enabled.", "type": "string" }, - "summary_cites": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false + "query": { + "description": "Natural-language search query.", + "example": "Which mode does the user prefer?", + "type": "string" }, - "unknown": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false + "query_apps": { + "description": "Whether to include app-aware knowledge retrieval. Applies to knowledge hybrid queries. Defaults to true when omitted; pass false to search files only.", + "example": true, + "type": "boolean" }, - "version": { - "example": 1, - "type": "integer" - } - }, - "type": "object" - }, - "search.ForcefulRelationEntry": { - "properties": { - "chunk": { - "$ref": "#/components/schemas/search.V2Chunk", - "example": { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } + "query_by": { + "$ref": "#/components/schemas/search.QueryBy", + "description": "Retrieval method to use for the query.", + "example": "hybrid" }, - "via": { - "$ref": "#/components/schemas/search.RelationVia" - } - }, - "type": "object" - }, - "search.ForcefulRelationsBucket": { - "description": "ForcefulRelations is the caller-declared relation bucket, carrying the\nfrom-\u003eto edge that additional_context discards when it flattens these\ninto a chunk-uuid map. Always present, so a caller can read it\nunconditionally.", - "properties": { - "declared": { + "query_forceful_relations": { + "deprecated": true, + "description": "Deprecated alias for follow_forceful_relations. Ignored when follow_forceful_relations is sent.", + "example": true, + "type": "boolean", + "x-deprecated": "true" + }, + "recency_bias": { + "description": "Recency boost applied to ranking, from `0.0` to `1.0`. Default `0.4`: it reorders results within a relevance gap of up to 0.4 but never buries a clearly more relevant result. Send `0` to disable recency; higher values favour newer content more strongly.", + "example": 0.2, + "type": "number" + }, + "sub_tenant_id": { + "deprecated": true, + "description": "Deprecated: use `collection` for one collection or `collections` for several. Do not send it together with a multi-collection selector.", + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated-since": "2.0.1" + }, + "sub_tenant_ids": { + "deprecated": true, + "description": "Deprecated: use `collections`, which accepts the same list or weighted object. Do not send both.", "example": [ + "sub_tenant_4567", + "sub_tenant_8901" + ], + "oneOf": [ { - "chunk": { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } + "example": [ + "finance", + "legal" + ], + "items": { + "type": "string" + }, + "maxItems": 100, + "minItems": 1, + "title": "List of collections", + "type": "array" + }, + { + "additionalProperties": { + "exclusiveMinimum": 0, + "multipleOf": 0.1, + "type": "number" + }, + "example": { + "finance": 1.5, + "legal": 0.8 + }, + "maxProperties": 100, + "minProperties": 1, + "title": "Weighted collections", + "type": "object" } ], + "x-deprecated": "true", + "x-deprecated-since": "2.0.1" + }, + "temporal_now": { + "description": "The time to treat as now for temporal reasoning, in ISO 8601. Set it when replaying past conversations or backfilling; otherwise durations to now and recency windows use the server's clock.", + "type": "string" + }, + "temporal_reasoning": { + "description": "Resolve time-based questions (current, as of, ranges, upcoming) and return the matching dated facts in `chunks[].temporal` and `llm_prompt`. Never changes which chunks are returned. Default `true`; send `false` to disable.", + "example": true, + "type": "boolean" + }, + "tenant_id": { + "deprecated": true, + "description": "deprecated: use database", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + }, + "titles": { + "description": "Optional exact document-title filter. Values are matched case-insensitively and ORed, resolved to source IDs, then the normal query pipeline runs within that source scope. When ids is also supplied, the two filters are intersected.", "items": { - "$ref": "#/components/schemas/search.ForcefulRelationEntry" + "type": "string" }, "type": "array", "uniqueItems": false }, - "inferred": { - "example": [ + "type": { + "allOf": [ { - "chunk": { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } + "$ref": "#/components/schemas/search.SourceType" } ], - "items": { - "$ref": "#/components/schemas/search.ForcefulRelationEntry" - }, - "type": "array", - "uniqueItems": false + "deprecated": true, + "description": "Deprecated: omit it. Kept for older databases. Selects the corpus: `knowledge` (the default), `memory` or `all`.", + "x-deprecated": true } }, "type": "object" }, - "search.GraphContext": { - "deprecated": true, - "description": "GraphContext is omitted entirely when graph_context is disabled on the\nrequest (pointer + omitempty), so the response carries no graph slice\ninstead of an empty-but-present object.", + "search.QueryResult": { + "description": "The four-key /query response body: chunks, graph, forceful_relations and llm_prompt, and nothing else.", "properties": { - "chunk_id_to_group_ids": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "description": "Mapping from chunk ID to the relation group IDs it participates in.", - "example": { - "HydraEmbeddings123_0": [ - "grp_1234" - ] + "chunks": { + "description": "Retrieved chunks, ranked. Each carries its own text, enrichment and received_at, and nothing else about its source: POST /context/list with its context_id in `ids` returns the source's title, type, collection and metadata.", + "items": { + "$ref": "#/components/schemas/search.QueryChunk" }, - "type": "object" + "type": "array" }, - "chunk_relations": { - "description": "Scored relation paths relevant to the query, grouped by chunk.", - "example": [ - { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "group_id": "grp_1234", - "relevancy_score": 0.87, - "source_chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] - } - ], + "forceful_relations": { + "description": "Chunks pulled in because the author declared forceful_relations at ingest. [] when none were declared or follow_forceful_relations was false.", "items": { - "$ref": "#/components/schemas/search.ScoredPathResponse" + "$ref": "#/components/schemas/search.QueryForcefulRelation" }, - "type": "array", - "uniqueItems": false + "type": "array" }, - "query_paths": { - "description": "Scored relation paths ranked by relevance to the query.", - "example": [ - { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "group_id": "grp_1234", - "relevancy_score": 0.87, - "source_chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] - } - ], + "graph": { + "description": "Graph paths, query paths first then chunk relations, deduplicated. [] when graph_context was false.", "items": { - "$ref": "#/components/schemas/search.ScoredPathResponse" + "$ref": "#/components/schemas/search.QueryGraphPath" }, - "type": "array", - "uniqueItems": false + "type": "array" + }, + "llm_prompt": { + "description": "The whole response rendered as markdown to inject verbatim into a model call: results, forceful relations, related facts (`[P1]`, `[P2]`, ... in `graph` order), temporal facts and sources. It also carries what no JSON key does: a computed duration, source facts, entity profiles and code-search answers. `\"\"` only when the query found nothing.", + "type": "string" } }, - "type": "object", - "x-deprecated": "true" + "required": [ + "chunks", + "graph", + "forceful_relations", + "llm_prompt" + ], + "type": "object" + }, + "search.RecallMode": { + "enum": [ + "fast", + "thinking", + "auto" + ], + "type": "string", + "x-enum-varnames": [ + "RecallModeFast", + "RecallModeThinking", + "RecallModeAuto" + ] }, - "search.GraphPath": { + "search.RelationVia": { "properties": { - "chunk_ids": { - "example": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "combined_context": { - "description": "Merged text from all chunk passages in this relation path.", - "example": "Acme Corp deploys HydraDB in production for context retrieval.", + "from": { + "description": "The `context_id` of the context that declared the relation.", "type": "string" }, - "relevancy_score": { - "description": "Relevance score for this item against the query.", - "example": 0.87, - "type": "number" - }, - "triplets": { - "description": "Knowledge-graph triplets that make up this relation path.", - "example": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ], - "items": { - "$ref": "#/components/schemas/search.PathTriplet" - }, - "type": "array", - "uniqueItems": false + "to": { + "description": "The `context_id` of the related context, the one the chunk belongs to.", + "type": "string" } }, "type": "object" }, - "search.GraphPlane": { - "description": "Graph is the consolidated graph plane: query_paths and\nchunk_relations consolidated into one ordered paths[] list, with each\npath carrying the chunk ids it supports so the caller no longer joins\nagainst chunk_id_to_group_ids. Populated whenever graph_context is on;\ngraph_context stays populated beside it.", + "search.SourceType": { + "description": "Deprecated corpus selector: `knowledge`, `memory` or `all`.", + "enum": [ + "knowledge", + "memory", + "all" + ], + "type": "string", + "x-enum-varnames": [ + "SourceKnowledge", + "SourceMemory", + "SourceAll" + ] + }, + "sources.MemoryDeleteResponse": { "properties": { - "paths": { + "deleted_count": { + "description": "Number of contexts deleted. `0` means no ID matched anything to delete.", + "example": 1, + "type": "integer" + }, + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" + }, + "results": { + "description": "One result per requested ID.", "example": [ { - "chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "relevancy_score": 0.87, - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] + "deleted": true, + "error": "", + "id": "HydraDoc1234" } ], "items": { - "$ref": "#/components/schemas/search.GraphPath" + "$ref": "#/components/schemas/sources.SourceDeleteResultItem" }, "type": "array", "uniqueItems": false - } - }, - "type": "object" - }, - "search.MetadataFilters": { - "additionalProperties": {}, - "description": "DEPRECATED: use `attributes`, which is an operator language pushed into the vector search rather than bare equality applied after it. `metadata_filters` keeps working, and is still the only way to filter on per-context custom_attributes, which `attributes` does not cover yet. Filters results by context metadata. Top-level keys target tenant metadata (for example department, priority, active, or tags). Nested additional_metadata keys target document metadata. Separate keys are ANDed. Each top-level key accepts an operator object naming the comparison: {\"contains\": value} matches sources whose field holds that value (multi-value fields are stored comma-joined, so this matches one member); {\"contains_any\": [values]} matches sources holding ANY one of the listed values; {\"equals\": value} matches sources whose field is exactly that value. The bare forms remain supported and unchanged but are deprecated in favour of the operators, because the comparison they perform is inferred from the JSON shape rather than stated: a bare scalar behaves as equals, a bare array as contains_any, and a bare single-element array as contains. Operators apply to top-level keys only; inside additional_metadata use the bare scalar or array forms. An operator used inside additional_metadata is NOT rejected - it is read as an exact-match filter against a stored object, so on a normal field it matches nothing and the request returns 200 with an empty result rather than an error. A known operator given the wrong operand type, or several operators in one object, is rejected with 400 VALIDATION_ERROR rather than silently matching nothing. A MISSPELLED operator is not: {\"contian\": \"x\"} is indistinguishable from a filter for a stored object with that key, so it is left alone and matches nothing. An object whose keys are not operator names is likewise treated as an exact-match filter against a stored object, unchanged. RESERVED NAMES: contains, contains_any and equals are reserved as the keys of a top-level filter object, so an object built only from them is read as an operator and is no longer available for exact object matching -- {\"f\": {\"contains\": \"x\"}} is read as the operator, and an object whose keys are ALL operator names is rejected with 400. A caller matching such an object in a JSON-typed field must rename the nested key or the field. Mixing an operator name with any other key ({\"contains\": \"a\", \"other\": 1}) is unaffected and still exact-matches. There is no ALL/AND operator within a single key. contains, contains_any and arrays are supported on VARCHAR fields only: any of them passed for a declared field of another type is rejected with 400 VALIDATION_ERROR. equals works on every declared type, so {\"priority\": {\"equals\": 7}} is valid on an INT64 field. Size limits: each list may hold at most 500 values, and the whole metadata_filters object is capped at 64 KiB measured on its compact JSON encoding in UTF-8 bytes AFTER operator objects are reduced to their values, so {\"contains\": \"x\"} is measured as [\"x\"] and the operator keyword itself costs nothing. The cap bounds the cost of the resulting vector-store expression, which the operator spelling does not change. Field names and punctuation count. Exceeding either returns 400 naming the offending key or the actual byte count.", - "example": { - "active": true, - "additional_metadata": { - "author": "ada" }, - "department": "finance", - "priority": 7, - "tags": [ - "alpha", - "beta" - ] + "success": { + "deprecated": true, + "description": "Deprecated: read `deleted_count` and `results` for what was removed, and the HTTP status for the request.", + "example": true, + "type": "boolean", + "x-deprecated": "true" + }, + "user_memory_deleted": { + "description": "Number of deletions from the memory corpus. Returned only on older databases, and only when `type` selects `memory` or `all`.", + "example": 1, + "type": "integer" + } }, "type": "object" }, - "search.Operator": { - "enum": [ - "or", - "and", - "phrase" - ], - "type": "string", - "x-enum-varnames": [ - "OperatorOr", - "OperatorAnd", - "OperatorPhrase" - ] - }, - "search.PathTriplet": { + "sources.SourceDeleteResultItem": { "properties": { - "relation": { - "additionalProperties": {}, - "description": "Relation properties including predicate and confidence score.", - "example": { - "confidence": 0.92, - "predicate": "works_at" - }, - "type": "object" + "deleted": { + "description": "Whether this context was deleted.", + "example": true, + "type": "boolean" }, - "source": { - "additionalProperties": {}, - "description": "Source entity of the relationship.", - "example": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "type": "object" + "error": { + "description": "Why this ID was not deleted; empty string on success.", + "example": "", + "type": "string" }, - "target": { - "additionalProperties": {}, - "description": "Target entity of the relationship.", - "example": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - }, - "type": "object" + "id": { + "description": "The requested context ID.", + "example": "HydraDoc1234", + "type": "string" } }, "type": "object" }, - "search.ProfileContext": { - "description": "ProfileContext/ProfileFilter surface the entity-profile block when the\nrequest named a profile_subject (PRO-1797); omitted otherwise.", + "sources.V2SourceDeleteRequest": { "properties": { - "entity_id": { - "description": "Unique identifier for this entity in the graph.", - "example": "entity_1a2b", + "collection": { + "description": "Collection the contexts belong to. Omit it to use the database's default collection.", + "example": "team_docs", + "type": "string" + }, + "database": { + "description": "Database the contexts belong to. Required. Formerly `tenant_id`; the alias is still accepted.", + "example": "acme_corp", "type": "string" }, - "entries": { + "ids": { + "description": "The IDs of the contexts to delete.", "example": [ - { - "confidence": 0.92 - } + "HydraDoc1234", + "HydraDoc4567" ], "items": { - "$ref": "#/components/schemas/search.ProfileEntry" + "type": "string" }, "type": "array", "uniqueItems": false }, - "headline": { - "type": "string" - }, - "name": { - "description": "Human-readable label for this resource.", - "example": "general", - "type": "string" - }, - "perspective": { - "type": "string" - }, - "subject": { - "type": "string" + "sub_tenant_id": { + "deprecated": true, + "description": "Deprecated: use `collection`.", + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" }, - "summary": { - "type": "string" + "tenant_id": { + "deprecated": true, + "description": "Deprecated: use `database`.", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" }, - "version": { - "example": 1, - "type": "integer" + "type": { + "deprecated": true, + "description": "Deprecated: omit it. Kept for older databases, where `memory` deletes from the memory corpus instead of `knowledge`.", + "enum": [ + "knowledge", + "memory", + "all" + ], + "example": "knowledge", + "type": "string", + "x-deprecated": true } }, "type": "object" }, - "search.ProfileEntry": { + "tenants.AttributeDataType": { + "description": "Data type of an attribute field. `ARRAY` cannot be declared on a new field: for several values, declare `VARCHAR` and store them comma-joined.", + "enum": [ + "BOOL", + "INT8", + "INT16", + "INT32", + "INT64", + "FLOAT", + "DOUBLE", + "VARCHAR", + "JSON", + "ARRAY" + ], + "type": "string", + "x-enum-varnames": [ + "DataTypeBool", + "DataTypeInt8", + "DataTypeInt16", + "DataTypeInt32", + "DataTypeInt64", + "DataTypeFloat", + "DataTypeDouble", + "DataTypeVarchar", + "DataTypeJSON", + "DataTypeArray" + ] + }, + "tenants.CollectionStats": { "properties": { - "confidence": { - "description": "Confidence score, from 0 to 1.", - "example": 0.92, - "type": "number" - }, - "facet": { - "type": "string" - }, - "since": { - "type": "string" - }, - "slot": { - "type": "string" - }, - "state": { - "description": "stated | observed | inferred | record", - "type": "string" - }, - "statement_keys": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "text": { - "type": "string" + "row_count": { + "description": "Total number of indexed rows in this collection.", + "example": 1280, + "type": "integer" } }, "type": "object" }, - "search.ProfileFilterInfo": { + "tenants.CustomPropertyDefinition": { "properties": { - "applied": { + "data_type": { + "allOf": [ + { + "$ref": "#/components/schemas/tenants.AttributeDataType" + } + ], + "description": "Data type of the attribute field, for example `VARCHAR`.", + "example": "VARCHAR" + }, + "enable_dense_embedding": { + "description": "Whether to enable semantic (dense) embedding search on this field.", "example": true, "type": "boolean" }, - "degraded": { + "enable_match": { + "description": "Whether to enable exact-match filtering on this field.", "example": true, "type": "boolean" }, - "entity_id": { - "description": "Unique identifier for this entity in the graph.", - "example": "entity_1a2b", - "type": "string" - }, - "found": { - "example": true, + "enable_sparse_embedding": { + "description": "Whether to enable BM25 (sparse) embedding search on this field.", + "example": false, "type": "boolean" }, - "selected_entries": { - "example": 1, + "max_length": { + "description": "Maximum string length in bytes for VARCHAR fields.", + "example": 256, "type": "integer" }, - "subject": { + "name": { + "description": "Field name. Immutable after database creation.", + "example": "category", "type": "string" - }, - "version": { - "example": 1, - "type": "integer" } }, "type": "object" }, - "search.QueryBy": { - "enum": [ - "hybrid", - "text" - ], - "type": "string", - "x-enum-varnames": [ - "QueryByHybrid", - "QueryByText" - ] - }, - "search.QueryChunk": { + "tenants.DatabaseDetail": { "properties": { - "chunk_id": { - "description": "The chunk's id. Every graph hop names the chunk it was extracted from by this id.", - "type": "string" - }, - "content": { - "description": "The chunk's own text. Enrichment is not concatenated into it.", - "type": "string" - }, - "context_id": { - "description": "The id of the context (source) the chunk belongs to.", - "type": "string" - }, - "enrichment": { - "description": "What enrichment produced for the chunk, kept apart from content. Absent when nothing was produced.", + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", "type": "string" }, - "enrichment_kind": { - "description": "The context_category the author declared at ingest (user_preference, business_knowledge or decision_trace). Never inferred. Absent when none was declared.", + "type": { + "description": "Storage layout the database was created with. `split` means separate knowledge and memory corpora, selected by `type` on each call. Absent where the layout is not exposed.", "enum": [ - "user_preference", - "business_knowledge", - "decision_trace" + "split" ], + "example": "split", "type": "string" - }, - "received_at": { - "description": "When the context this chunk belongs to was received (RFC 3339). This is the ingest time, not the caller's happened_at, which is not echoed here. Omitted when the store holds no receipt time for the row (older rows); it is never sent empty.", - "type": "string" - }, - "score": { - "description": "Relevance after reranking.", - "type": "number" - }, - "temporal": { - "description": "Dated facts extracted from the chunk. Present only when the query engaged temporal reasoning.", - "items": { - "$ref": "#/components/schemas/search.QueryChunkTemporal" - }, - "type": "array" } }, - "required": [ - "chunk_id", - "context_id", - "score", - "content" - ], "type": "object" }, - "search.QueryChunkTemporal": { + "tenants.FailedTenant": { "properties": { - "content": { - "description": "The fact as a sentence with its dates embedded.", + "database": { + "description": "Database identifier that failed provisioning.", + "example": "acme_corp", "type": "string" }, - "end_date": { - "description": "End of the fact's window, YYYY-MM-DD, or null.", - "type": [ - "string", - "null" - ] - }, - "start_date": { - "description": "Start of the fact's window, YYYY-MM-DD, or null.", - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "content", - "start_date", - "end_date" - ], - "type": "object" - }, - "search.QueryForcefulRelation": { - "properties": { - "chunk": { - "$ref": "#/components/schemas/search.QueryChunk" + "error": { + "description": "Error message explaining why the database failed.", + "example": "", + "type": "string" }, - "via": { - "$ref": "#/components/schemas/search.RelationVia", - "description": "The declared edge that pulled the chunk in: from is the context that declared it, to is the chunk's own context." + "tenant_id": { + "deprecated": true, + "description": "Deprecated: use `database`.", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" } }, - "required": [ - "via", - "chunk" - ], "type": "object" }, - "search.QueryGraphEdge": { + "tenants.InfraStatusResponseV2": { "properties": { - "chunk_id": { - "description": "The chunk the relation was extracted from. For a chunk_relation path this is the returned chunk the path hangs under.", + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", "type": "string" }, - "context": { - "description": "The sentence the relation was extracted from.", - "type": "string" + "infra": { + "$ref": "#/components/schemas/tenants.InfraV2", + "example": { + "graph_status": true, + "ready_for_ingestion": true, + "scheduler_status": true, + "vectorstore_status": { + "knowledge": true, + "memories": true + } + } }, - "predicate": { - "description": "The relation between the two entities.", + "message": { + "description": "Human-readable result message.", + "example": "Success", "type": "string" }, - "relationship_id": { - "description": "The relation's stable id.", + "org_id": { + "description": "Organization that owns this resource.", + "example": "org_1a2b3c", "type": "string" }, - "temporal_details": { - "description": "When the relation held, as extraction phrased it.", - "type": "string" + "tenant_id": { + "deprecated": true, + "description": "Deprecated: use `database`.", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" }, - "timestamp": { - "type": [ - "number", - "null" - ] + "type": { + "description": "Storage layout the database was created with (`split`: separate knowledge and memory corpora). Absent while the database is deleting or its layout is unknown.", + "enum": [ + "split" + ], + "example": "split", + "type": "string" } }, - "required": [ - "predicate", - "context", - "relationship_id", - "chunk_id" - ], "type": "object" }, - "search.QueryGraphEntity": { + "tenants.InfraV2": { "properties": { - "entity_id": { - "type": "string" + "graph_status": { + "description": "Whether the graph store is healthy for this database.", + "example": true, + "type": "boolean" }, - "name": { - "type": "string" + "ready_for_ingestion": { + "description": "True once the database is fully provisioned (`scheduler_status`, `graph_status` and both `vectorstore_status` corpora ready) and can accept ingestion and serve queries. Creation is asynchronous, so poll GET /databases/status until this is true before ingesting or querying.", + "example": true, + "type": "boolean" + }, + "scheduler_status": { + "description": "Whether lifecycle provisioning has finished for this database (creation_status is ready). False while the database is still being created, even if individual collections already exist.", + "example": true, + "type": "boolean" + }, + "vectorstore_status": { + "$ref": "#/components/schemas/tenants.VectorstoreStatusV2", + "example": { + "knowledge": true, + "memories": true + } } }, - "required": [ - "entity_id", - "name" - ], "type": "object" }, - "search.QueryGraphPath": { + "tenants.SubTenantDeleteResponse": { "properties": { - "origin": { - "description": "Which lane found the path: query_path (grown from the entities in the query) or chunk_relation (the neighbourhood of a returned chunk).", - "enum": [ - "query_path", - "chunk_relation" - ], + "collection": { + "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", + "example": "team_docs", "type": "string" }, - "path_summary": { - "description": "The path narrated as one sentence.", + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", "type": "string" }, - "triplets": { - "description": "The path's hops, in order.", - "items": { - "$ref": "#/components/schemas/search.QueryGraphTriplet" - }, - "type": "array" - } - }, - "required": [ - "origin", - "triplets", - "path_summary" - ], - "type": "object" - }, - "search.QueryGraphTriplet": { - "properties": { - "relation": { - "$ref": "#/components/schemas/search.QueryGraphEdge" + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" }, - "source": { - "$ref": "#/components/schemas/search.QueryGraphEntity" + "status": { + "description": "Current lifecycle or processing state.", + "example": "completed", + "type": "string" }, - "target": { - "$ref": "#/components/schemas/search.QueryGraphEntity" + "sub_tenant_id": { + "deprecated": true, + "description": "Deprecated: use `collection`.", + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + }, + "tenant_id": { + "deprecated": true, + "description": "Deprecated: use `database`.", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" } }, - "required": [ - "source", - "relation", - "target" - ], "type": "object" }, - "search.QueryRequest": { + "tenants.SubTenantIdsResponse": { "properties": { - "acl": { - "description": "ACL scopes retrieval to documents the given principals may access\n(PRO-1684 document ACLs): a document matches when its stored ACL is\nempty (unrestricted, pre-RBAC content and connectors without permission\nsupport), contains __public__, or intersects these principals. Entries\nare bare emails or prefixed principals (user_email:/group:/domain:).\nOmitted, empty, or [\"*\"] disables ACL filtering entirely, today's\nbehavior. Like IDs, the resulting clause survives the metadata\nzero-result retry. An entry that is not a known principal fails CLOSED:\nit matches only public and unrestricted documents, never restricted.", + "collections": { + "description": "List of collection identifiers for this database.", + "example": [ + "team_docs", + "engineering" + ], "items": { "type": "string" }, "type": "array", "uniqueItems": false }, - "additional_context": { - "description": "Optional context string prepended to the query to improve retrieval relevance.", - "example": "The user is a senior engineer onboarding to the platform.", - "type": "string" - }, - "alpha": { - "description": "Weighting balance between dense and sparse retrieval in hybrid mode. `\"auto\"` lets HydraDB choose; a number from 0 (full BM25) to 1 (full dense) sets it explicitly." - }, - "attributes": { - "additionalProperties": {}, - "description": "Attributes is the go-forward metadata filter: a MongoDB-like operator query\n($eq/$ne/$gt/$gte/$lt/$lte/$in/$nin/$and/$or/$not/$exists) over the\ndatabase attributes, translated to a safe Milvus scalar pre-filter by\nBuildAttributesFilterExpr (PRO-1618). It composes (AND) with the\ndeprecated metadata_filters while both exist. Field names are allowlisted\nand values escaped, so it is injection-safe.\n\nIt is applied everywhere metadata_filters is, and nowhere else: the\nchunks a query returns, the additional context and forceful-relation\nchunks (the fail-closed post-filter net in the service), and the graph\npaths, which the graph lane prunes by resolving every source a path\ncites and dropping the paths that touch one failing the predicate\n(disallowedGraphSources). Product decision 2026-09-04: `attributes`\nbehaves like `metadata_filters` on every part of the response.", - "type": "object" - }, - "code_search": { - "description": "CodeSearch forces the repository code-search branch on (true) or off\n(false) for this query, overriding the classifier. Nil = let the\nclassifier decide. Only meaningful where the branch is enabled.", - "example": true, - "type": "boolean" - }, - "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", - "example": "team_docs", + "message": { + "description": "Human-readable result message.", + "example": "Success", "type": "string" }, - "collections": { - "description": "Preferred /query scope selector. Send either a list of collection IDs for equal normalized weighting, or an object mapping collection ID to a positive relative ranking weight with at most one decimal place. Do not send together with the deprecated sub_tenant_ids or sub_tenant_id.", + "sub_tenant_ids": { + "deprecated": true, + "description": "Deprecated alias for `collections`.", "example": [ - "team_docs", - "engineering" - ], - "oneOf": [ - { - "example": [ - "finance", - "legal" - ], - "items": { - "type": "string" - }, - "maxItems": 100, - "minItems": 1, - "type": "array" - }, - { - "additionalProperties": { - "exclusiveMinimum": 0, - "multipleOf": 0.1, - "type": "number" - }, - "example": { - "finance": 1.5, - "legal": 0.8 - }, - "maxProperties": 100, - "minProperties": 1, - "type": "object" - } - ], - "x-preferred": true - }, - "database": { - "description": "Database is the canonical v2 name for the tenant scope. TenantID is its\ndeprecated alias and remains fully accepted. The TenantAliases middleware\nreconciles the two before binding, so TenantID is always populated and the\nhandler reads it; Database/Collection are carried only for docs/OpenAPI.", - "example": "acme_corp", - "type": "string" - }, - "follow_forceful_relations": { - "description": "Whether to follow the relations the author declared at ingest (forceful_relations) and return the related contexts. Defaults to true when omitted.", - "example": true, - "type": "boolean" - }, - "graph_context": { - "description": "Whether to include graph context in the response. Defaults to true for /query when omitted.", - "example": true, - "type": "boolean" - }, - "graph_vector_prune": { - "description": "GraphVectorPrune switches the graph-connected-chunks lane from \"fetch\ngraph-selected chunks and let the fusion reranker sort them out\" to \"fetch\na wider graph-selected candidate pool, then rank that pool by Milvus vector\nsimilarity, fully replacing the final chunk list.\" Works in either fast or\nthinking mode. Default false preserves existing behavior. Also gated\nserver-side by a repo-level config flag (SearchService's\ngraphVectorPruneEnabled) — if that flag is off, this is forced to false\nregardless of what the request sets, so a deployment can disable the\nmechanism without any client-side change.", - "example": true, - "type": "boolean" - }, - "graph_vector_prune_spacy_entities": { - "description": "GraphVectorPruneSpacyEntities: when GraphVectorPrune is also set, swaps the\ngraph lane's entity-extraction source from the default LLM-based extractor\nto a local spaCy subprocess (faster, no network round trip, but a\nnarrower/mismatched entity vocabulary versus the graph's own LLM-extracted\nnode names). No-op if GraphVectorPrune is false (including when forced\nfalse by the server-level flag) or no spaCy extractor was configured at\nstartup.", - "example": true, - "type": "boolean" - }, - "ids": { - "description": "IDs optionally scopes retrieval to specific source ids. The v2 wire field is\n`ids` (matching /context/list); empty means search the whole corpus. Applied\nas a Milvus `source_id in [...]` pre-filter that is preserved across the\nmetadata zero-result retry, so a source-scoped search that matches nothing\nreturns nothing rather than silently widening to the whole corpus.", - "example": [ - "HydraDoc1234", - "HydraDoc4567" + "sub_tenant_4567", + "sub_tenant_8901" ], "items": { "type": "string" }, "type": "array", - "uniqueItems": false - }, - "max_results": { - "description": "Maximum number of chunks to return.", - "example": 10, - "type": "integer" - }, - "metadata_filters": { - "$ref": "#/components/schemas/search.MetadataFilters", - "deprecated": true, - "x-deprecated": true - }, - "mode": { - "$ref": "#/components/schemas/search.RecallMode", - "example": "thinking" - }, - "num_related_chunks": { - "description": "Number of adjacent chunks to pull alongside each matched chunk for additional context.", - "example": 3, - "type": "integer" - }, - "operator": { - "$ref": "#/components/schemas/search.Operator", - "example": "and" - }, - "profile_entity_type": { - "description": "ProfileEntityType/ProfileNamespace refine the subject's graph identity;\ndefaults (\"PERSON\"/\"users\") cover the common case of a person subject.", - "type": "string" - }, - "profile_namespace": { + "uniqueItems": false, + "x-deprecated": "true" + } + }, + "type": "object" + }, + "tenants.TenantCreateAcceptedResponse": { + "properties": { + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", "type": "string" }, - "profile_subject": { - "description": "ProfileSubject names the entity whose compiled profile should ride the\nresponse as profile_context/profile_filter (PRO-1797). Payload-only:\nchunk ranking is never altered. Omitted = no profile block. Dark until\nthe repo-level ENTITY_PROFILE_CONTEXT_ENABLED flag is on.", + "message": { + "description": "Human-readable result message.", + "example": "Success", "type": "string" }, - "query": { - "description": "Natural-language search query.", - "example": "Which mode does the user prefer?", + "status": { + "description": "Current lifecycle or processing state.", + "example": "completed", "type": "string" }, - "query_apps": { - "description": "Whether to include app-aware knowledge retrieval. Applies to knowledge hybrid queries. Defaults to true when omitted; pass false to search files only.", - "example": true, - "type": "boolean" - }, - "query_by": { - "$ref": "#/components/schemas/search.QueryBy", - "description": "Retrieval method to use for the query.", - "example": "hybrid" - }, - "query_forceful_relations": { - "deprecated": true, - "description": "Deprecated alias for follow_forceful_relations. Ignored when follow_forceful_relations is sent.", - "example": true, - "type": "boolean", - "x-deprecated": "true" - }, - "recency_bias": { - "description": "Recency boost applied to ranking (0.0-1.0). Omit it to get the always-on default baseline of 0.40 (a bounded \u003c=40% swing on normalized relevance — it reorders within a relevance gap of up to 0.40 but never buries a more strongly relevant result); send 0 to disable recency entirely; higher values favour more recent sources more strongly.", - "example": 0.2, - "type": "number" - }, - "sub_tenant_id": { + "tenant_id": { "deprecated": true, - "description": "Deprecated for /query (since 2.0.1). Use collection for a single scope or collections for multiple. Backwards-compatible and will be removed in a future version. Do not send together with a multi-scope selector.", - "example": "sub_tenant_4567", + "description": "Deprecated: use `database`.", + "example": "tenant_1234", "type": "string", - "x-deprecated-since": "2.0.1" + "x-deprecated": "true" + } + }, + "type": "object" + }, + "tenants.TenantCreateRequest": { + "properties": { + "database": { + "description": "Name of the database to create. Formerly `tenant_id`, which is still accepted.", + "example": "acme_corp", + "type": "string" }, - "sub_tenant_ids": { - "deprecated": true, - "description": "Deprecated for /query (since 2.0.1). Use collections instead; it accepts the same list or weighted-object shape. Backwards-compatible and will be removed in a future version. Do not send together with collections.", + "database_metadata_schema": { + "description": "Defines database-level metadata fields for exact-match filtering and semantic/BM25 search. Canonical name; `tenant_metadata_schema` is a deprecated alias. Schema field names are immutable after database creation.", "example": [ - "sub_tenant_4567", - "sub_tenant_8901" - ], - "oneOf": [ - { - "example": [ - "finance", - "legal" - ], - "items": { - "type": "string" - }, - "maxItems": 100, - "minItems": 1, - "type": "array" - }, { - "additionalProperties": { - "exclusiveMinimum": 0, - "multipleOf": 0.1, - "type": "number" - }, - "example": { - "finance": 1.5, - "legal": 0.8 - }, - "maxProperties": 100, - "minProperties": 1, - "type": "object" + "data_type": "VARCHAR", + "enable_dense_embedding": true, + "enable_match": true, + "enable_sparse_embedding": false, + "max_length": 256, + "name": "category" } ], - "x-deprecated": "true", - "x-deprecated-since": "2.0.1" - }, - "temporal_intent": { - "$ref": "#/components/schemas/search.TemporalIntentOverride", - "example": { - "duration_to_now": true, - "mode": "thinking" - } + "items": { + "$ref": "#/components/schemas/tenants.CustomPropertyDefinition" + }, + "type": "array", + "uniqueItems": false }, - "temporal_now": { - "description": "TemporalNow optionally anchors \"now\" for temporal reasoning (ISO-8601).\nCallers replaying past conversations (or backfilling) must supply it or\nto-now durations and recency windows resolve against the server's wall\nclock (LongMemEval measured 0 exact to-now durations from this alone).", - "type": "string" + "embeddings_dimension": { + "description": "Override for the embedding vector dimension. Default: 1536.", + "example": 1536, + "type": "integer" }, - "temporal_reasoning": { - "description": "TemporalReasoning activates the temporal read path: the query is classified\ninto a temporal mode (current/as-of/range/upcoming...), matching edge-level\ntemporal facts are resolved from the edge_temporal store and ride back on\nthe response (temporal_facts / temporal_duration / temporal_filter).\nCONTRACT: chunk ranking is NEVER altered — ON returns the same chunks as\nOFF; the layer is additive payload + computed answers only (rank shaping\nmeasured net-negative on BEAM/LongMemEval/TEMPO; see temporal_filters.go).\nOptional; ON by default — pass temporal_reasoning:false to disable.\nResolved by GetTemporalReasoningOrDefault (ownership rule).", - "example": true, + "is_embeddings_tenant": { + "description": "Internal flag for embedding-only databases.", + "example": false, "type": "boolean" }, "tenant_id": { @@ -7014,146 +5628,153 @@ "type": "string", "x-deprecated": "true" }, - "titles": { - "description": "Optional exact document-title filter. Values are matched case-insensitively and ORed, resolved to source IDs, then the normal query pipeline runs within that source scope. When ids is also supplied, the two filters are intersected.", + "tenant_metadata_schema": { + "deprecated": true, + "description": "deprecated: use database_metadata_schema", "items": { - "type": "string" + "$ref": "#/components/schemas/tenants.CustomPropertyDefinition" }, "type": "array", - "uniqueItems": false + "uniqueItems": false, + "x-deprecated": "true" }, "type": { - "$ref": "#/components/schemas/search.SourceType", + "allOf": [ + { + "$ref": "#/components/schemas/github_com_hydradb_hydradb-application_internal_platform_storagelayout.Layout" + } + ], "deprecated": true, - "description": "Deprecated: kept for split databases. Corpus to query: knowledge (the default), memory, or all (both, merged).", - "x-deprecated": true + "description": "Deprecated: omit it. `split` creates an older-style database with separate knowledge and memory corpora." } }, "type": "object" }, - "search.QueryResult": { - "description": "The four-key /query response body: chunks, graph, forceful_relations and llm_prompt, and nothing else.", + "tenants.TenantDeleteResponse": { "properties": { - "chunks": { - "description": "Retrieved chunks, ranked. Each carries its own text, enrichment and received_at, and nothing else about its source: POST /context/list with its context_id in `ids` returns the source's title, type, collection and metadata.", - "items": { - "$ref": "#/components/schemas/search.QueryChunk" - }, - "type": "array" - }, - "forceful_relations": { - "description": "Chunks pulled in because the author declared forceful_relations at ingest. [] when none were declared or follow_forceful_relations was false.", - "items": { - "$ref": "#/components/schemas/search.QueryForcefulRelation" - }, - "type": "array" + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", + "type": "string" }, - "graph": { - "description": "Graph paths, query paths first then chunk relations, deduplicated. [] when graph_context was false.", - "items": { - "$ref": "#/components/schemas/search.QueryGraphPath" - }, - "type": "array" + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" }, - "llm_prompt": { - "description": "The whole response rendered as markdown for a model call: numbered results with their relevance, forceful relations, related facts labelled P1..Pn in `graph` order and citing the results they came from, temporal facts and sources. It also carries what this body has no key for: a computed duration, source facts, entity profiles, the code-search answer, the aliases and references the query was expanded with, decision-trace evidence, and a note when a lookup failed or was truncated. Inject it verbatim.", + "status": { + "description": "Current lifecycle or processing state.", + "example": "completed", "type": "string" + }, + "tenant_id": { + "deprecated": true, + "description": "Deprecated: use `database`.", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" } }, - "required": [ - "chunks", - "graph", - "forceful_relations", - "llm_prompt" - ], "type": "object" }, - "search.RecallMode": { - "enum": [ - "fast", - "thinking", - "auto" - ], - "type": "string", - "x-enum-varnames": [ - "RecallModeFast", - "RecallModeThinking", - "RecallModeAuto" - ] - }, - "search.RelationVia": { + "tenants.TenantIdsResponse": { "properties": { - "from": { - "type": "string" + "databases": { + "description": "List of database identifiers.", + "example": [ + "acme_corp", + "research_kb" + ], + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false }, - "to": { - "type": "string" - } - }, - "type": "object" - }, - "search.ResolvedReference": { - "properties": { - "expression": { - "type": "string" + "details": { + "description": "One entry per live database, with its storage layout.", + "example": [ + { + "database": "acme_corp", + "type": "split" + } + ], + "items": { + "$ref": "#/components/schemas/tenants.DatabaseDetail" + }, + "type": "array", + "uniqueItems": false }, - "resolved_to": { - "type": "string" - } - }, - "type": "object" - }, - "search.ScoredPathResponse": { - "properties": { - "combined_context": { - "description": "Merged text from all chunk passages in this relation path.", - "example": "Acme Corp deploys HydraDB in production for context retrieval.", - "type": "string" + "failed_databases": { + "description": "Databases that failed provisioning, with error details.", + "example": [ + { + "database": "acme_corp", + "error": "", + "tenant_id": "tenant_1234" + } + ], + "items": { + "$ref": "#/components/schemas/tenants.FailedTenant" + }, + "type": "array", + "uniqueItems": false }, - "group_id": { - "description": "Unique identifier for this relation group.", - "example": "grp_1234", - "type": "string" + "failed_tenant_ids": { + "deprecated": true, + "description": "Deprecated alias for `failed_databases`.", + "example": [ + { + "database": "acme_corp", + "error": "", + "tenant_id": "tenant_1234" + } + ], + "items": { + "$ref": "#/components/schemas/tenants.FailedTenant" + }, + "type": "array", + "uniqueItems": false, + "x-deprecated": "true" }, - "relevancy_score": { - "description": "Relevance score for this item against the query.", - "example": 0.87, - "type": "number" + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" }, - "source_chunk_ids": { - "description": "IDs of the chunks that contribute to this relation path.", + "tenant_ids": { + "deprecated": true, + "description": "Deprecated alias for `databases`.", "example": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" + "tenant_1234", + "tenant_5678" ], "items": { "type": "string" }, "type": "array", - "uniqueItems": false - }, - "triplets": { - "description": "Knowledge-graph triplets that make up this relation path.", + "uniqueItems": false, + "x-deprecated": "true" + } + }, + "type": "object" + }, + "tenants.TenantMetadataSchemaUpdateRequest": { + "properties": { + "add_fields": { + "description": "New attribute fields to add to the database schema. Additive only: no deletes, renames or type changes.", "example": [ { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } + "data_type": "VARCHAR", + "enable_dense_embedding": true, + "enable_match": true, + "enable_sparse_embedding": false, + "max_length": 256, + "name": "category" } ], "items": { - "$ref": "#/components/schemas/search.PathTriplet" + "$ref": "#/components/schemas/tenants.CustomPropertyDefinition" }, "type": "array", "uniqueItems": false @@ -7161,393 +5782,223 @@ }, "type": "object" }, - "search.SourceFact": { + "tenants.TenantStatsResponse": { "properties": { - "actor": { - "type": "string" - }, - "actor_role": { - "type": "string" - }, - "app_kind": { - "description": "App integration category, populated for connector-synced sources.", - "example": "slack", - "type": "string" - }, - "chunk_id": { - "description": "Chunk that provides evidence for this relation.", - "example": "HydraEmbeddings123_0", - "type": "string" - }, - "connector": { - "type": "string" - }, - "container": { - "type": "string" - }, - "provider": { - "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", - "example": "slack", + "database": { + "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", + "example": "acme_corp", "type": "string" }, - "relation": { - "type": "string" + "knowledge_collection": { + "$ref": "#/components/schemas/tenants.CollectionStats", + "example": { + "row_count": 1280 + } }, - "relationship_id": { - "description": "Unique identifier for this relationship instance.", - "example": "rel_1234", - "type": "string" + "memory_collection": { + "$ref": "#/components/schemas/tenants.CollectionStats", + "example": { + "row_count": 1280 + } }, - "source_id": { - "example": "HydraDoc1234", + "message": { + "description": "Human-readable result message.", + "example": "Success", "type": "string" }, - "synced_at": { - "example": 1, - "type": "integer" - }, - "thread_id": { - "type": "string" + "tenant_id": { + "deprecated": true, + "description": "Deprecated: use `database`.", + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" } }, "type": "object" }, - "search.SourceFilterInfo": { - "description": "SourceFilter reports what the source layer did for this request.", + "tenants.VectorstoreStatusV2": { "properties": { - "actor_scope": { - "type": "string" - }, - "applied": { - "example": true, - "type": "boolean" - }, - "container_scope": { - "type": "string" - }, - "degraded": { - "example": true, - "type": "boolean" - }, - "matched_facts": { - "example": 1, - "type": "integer" - }, - "mode": { - "example": "thinking", - "type": "string" - }, - "provider": { - "description": "External provider being synced (e.g. `slack`, `github`, `linear`, `notion`, `gmail`).", - "example": "slack", - "type": "string" - }, - "thread_scope": { + "knowledge": { + "description": "Whether the knowledge vector store is healthy.", "example": true, "type": "boolean" }, - "truncated": { + "memories": { + "description": "Whether the memories vector store is healthy.", "example": true, "type": "boolean" } }, "type": "object" }, - "search.SourceInfo": { + "webhooks.DeliveryItem": { "properties": { - "additional_metadata": { - "additionalProperties": {}, - "description": "Per-document free-form metadata.", - "example": { - "author": "ada", - "doc_version": 3 - }, - "type": "object" + "attempts": { + "description": "Number of delivery attempts made.", + "example": 1, + "type": "integer" }, - "app_external_id": { - "description": "Provider-assigned identifier for this source (e.g. Slack channel ID).", - "example": "C0123456789", + "created_at": { + "description": "When this delivery was created (RFC 3339).", + "example": "2026-07-02T10:00:00Z", "type": "string" }, - "app_kind": { - "description": "App-source fields (populated when the source comes from an app integration).\nDefault null on the wire when absent.", - "example": "slack", + "delivery_id": { + "description": "Unique identifier for this webhook delivery attempt.", + "example": "dlv_9f8e7d6c", "type": "string" }, - "app_provider": { - "description": "Provider name for app-sourced items (e.g. `slack`, `github`).", - "example": "slack", + "doc_id": { + "description": "Source ID that triggered this delivery.", + "example": "HydraDoc1234", "type": "string" }, - "collection": { - "description": "Collection this source belongs to. Canonical name; mirrors the deprecated `sub_tenant_id` alias.", - "example": "team_docs", + "error_code": { + "description": "Machine-readable error code, empty string on success.", + "example": "", "type": "string" }, - "description": { - "description": "Human-readable description of the source.", - "example": "Internal overview of the Project Phoenix rollout.", + "error_message": { + "description": "Human-readable error description, empty string on success.", + "example": "", "type": "string" }, - "id": { - "description": "Unique identifier for this resource.", - "example": "HydraDoc1234", + "event_type": { + "description": "Event that triggered this delivery (e.g. `indexing.status_changed`).", + "example": "indexing.status_changed", "type": "string" }, - "metadata": { - "additionalProperties": {}, - "description": "Pydantic aliases (see VectorStoreChunk). Source metadata defaults to {} on\nthe wire (Python default_factory=dict), unlike chunk metadata which is null.", - "example": { - "department": "finance", - "priority": 7 - }, - "type": "object" - }, - "sub_tenant_id": { - "deprecated": true, - "description": "deprecated: use collection", - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - }, - "timestamp": { - "description": "RFC3339 timestamp associated with this item.", - "example": "2026-07-02T10:00:00Z", + "indexing_status": { + "description": "Current processing state: `queued`, `processing`, `completed`, or `failed`.", + "example": "completed", "type": "string" }, - "title": { - "description": "Title or name of the source.", - "example": "Project Phoenix Overview", + "status": { + "description": "Current delivery status (e.g. `completed`, `failed`, `permanently_failed`).", + "example": "completed", "type": "string" }, - "type": { - "description": "Source content category (e.g. `knowledge`, `memory`).", - "example": "knowledge", + "updated_at": { + "description": "RFC3339 timestamp of the most recent update.", + "example": "2026-07-02T10:00:05Z", "type": "string" }, - "url": { - "description": "URL to the original source, if available.", - "example": "https://docs.hydradb.com/phoenix", + "webhook_url": { + "description": "The endpoint this delivery was sent to: the URL registered when the delivery was created.", "type": "string" } }, "type": "object" }, - "search.SourceType": { - "description": "Source is the wire field `type` (Python QueryRequest.source has alias=\"type\").\nSourceLegacy accepts the pre-rename `source` key (Python populate_by_name=True\nkeeps the field name valid on input); resolveSourceAlias folds it into Source.", - "enum": [ - "knowledge", - "memory", - "all" - ], - "type": "string", - "x-enum-varnames": [ - "SourceKnowledge", - "SourceMemory", - "SourceAll" - ] - }, - "search.TemporalDuration": { - "description": "TemporalDuration is the computed event-duration answer, when resolved.", + "webhooks.DeliveryListResponse": { "properties": { - "approximate": { - "description": "Approximate is set when either endpoint's granularity is coarser than a\nday (month/year brackets) — the day count is then a floor-to-floor\nestimate, not an exact span; consumers should not present it as exact.", - "example": true, - "type": "boolean" - }, - "days": { - "example": 1, + "count": { + "description": "Number of deliveries returned.", + "example": 12, "type": "integer" }, - "from": { - "$ref": "#/components/schemas/search.TemporalFact", - "example": { - "chunk_id": "HydraEmbeddings123_0", - "event_end": 1, - "event_start": 1, - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "status": "completed" - } - }, - "from_date": { - "type": "string" - }, - "pairing_confidence": { - "description": "PairingConfidence is the normalized pair-scorer margin (0..1); low values\nmean the endpoints were weakly anchored to the question. Durations whose\nendpoints share no entity token with the question are suppressed\nentirely (P4: a wrong confident day count misleads answerers).", - "example": 0.5, - "type": "number" - }, - "to": { - "$ref": "#/components/schemas/search.TemporalFact", - "example": { - "chunk_id": "HydraEmbeddings123_0", - "event_end": 1, - "event_start": 1, - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "status": "completed" - } + "deliveries": { + "description": "List of webhook delivery attempt records.", + "example": [ + { + "attempts": 1, + "created_at": "2026-07-02T10:00:00Z", + "delivery_id": "dlv_9f8e7d6c", + "doc_id": "HydraDoc1234", + "error_code": "", + "error_message": "", + "event_type": "indexing.status_changed", + "indexing_status": "completed", + "status": "completed", + "updated_at": "2026-07-02T10:00:05Z" + } + ], + "items": { + "$ref": "#/components/schemas/webhooks.DeliveryItem" + }, + "type": "array", + "uniqueItems": false }, - "to_date": { + "next_cursor": { + "description": "Opaque pagination cursor for the next page; null or absent when no more pages.", + "example": "eyJvZmZzZXQiOjUwfQ==", "type": "string" } }, "type": "object" }, - "search.TemporalFact": { + "webhooks.RetryResponse": { "properties": { - "chunk_id": { - "description": "Chunk that provides evidence for this relation.", - "example": "HydraEmbeddings123_0", - "type": "string" - }, - "date_precision": { - "description": "DatePrecision is the resolution of the resolved dates: \"day\", \"month\",\n\"year\" (coarser-than-day dates are floored to bracket starts).", - "type": "string" - }, - "event_end": { - "example": 1, - "type": "integer" - }, - "event_start": { - "example": 1, - "type": "integer" - }, - "evidence_phrase": { - "description": "EvidencePhrase is the verbatim source phrase the dates were resolved\nfrom (e.g. \"today\", \"two weeks ago\").", - "type": "string" - }, - "fact_type": { - "type": "string" - }, - "object": { - "type": "string" - }, - "relation": { - "type": "string" - }, - "relationship_id": { - "description": "Unique identifier for this relationship instance.", - "example": "rel_1234", - "type": "string" - }, - "source_id": { - "example": "HydraDoc1234", + "delivery_id": { + "description": "Unique identifier for this webhook delivery attempt.", + "example": "dlv_9f8e7d6c", "type": "string" }, - "status": { - "description": "Current lifecycle or processing state.", - "example": "completed", + "message": { + "description": "Human-readable result message.", + "example": "Success", "type": "string" }, - "subject": { - "type": "string" + "queued": { + "description": "Whether the retry was successfully queued.", + "example": true, + "type": "boolean" } }, "type": "object" }, - "search.TemporalFilterInfo": { - "description": "TemporalFilter reports what the temporal layer did for this request.", + "webhooks.WebhookDeleteResponse": { "properties": { - "applied": { - "description": "Applied is true when the temporal layer engaged for a classified temporal\nquery — including when it matched zero dated facts; MatchedFacts carries the\nactual count. It is false only when the fact lookup degraded (Degraded).", - "example": true, - "type": "boolean" - }, - "chunk_scope": { - "example": 1, - "type": "integer" - }, - "degraded": { - "description": "Degraded is true when the fact lookup FAILED (as opposed to matching\nnothing) — callers must not read an empty payload as \"no temporal facts\nexist\" when this is set.", + "deleted": { + "description": "Whether the webhook was deleted.", "example": true, "type": "boolean" }, - "matched_facts": { - "example": 1, - "type": "integer" - }, - "mode": { - "example": "thinking", - "type": "string" - }, - "promoted": { - "example": 1, - "type": "integer" - }, - "scope": { - "description": "Scope reports how the chunk scope was applied: \"soft\" (bounded ranking\npromotion) or \"\" (no scope). Hard scoping was removed after TEMPO.", + "message": { + "description": "Human-readable result message.", + "example": "Success", "type": "string" - }, - "truncated": { - "example": true, - "type": "boolean" } }, "type": "object" }, - "search.TemporalIntentOverride": { - "description": "TemporalIntent (EXPERIMENTAL) lets the caller supply the classification\n(mode/window/phrases) directly, bypassing the regex classifier — for\nagents whose own LLM already understands the query, and for non-English\nqueries. Invalid overrides fall back to the classifier.", + "webhooks.WebhookGetResponse": { "properties": { - "cutoff": { - "type": "string" - }, - "duration_to_now": { - "example": true, - "type": "boolean" - }, - "event_phrases": { + "event_types": { + "description": "Event types to subscribe to (e.g. `[\"indexing.status_changed\"]`).", + "example": [ + "indexing.status_changed" + ], "items": { "type": "string" }, "type": "array", "uniqueItems": false }, - "mode": { - "example": "thinking", - "type": "string" + "registered": { + "description": "Whether a webhook is registered for this API key.", + "example": true, + "type": "boolean" }, - "window_end": { - "type": "string" + "signing_secret_configured": { + "description": "Whether a signing secret has been configured for payload verification.", + "example": true, + "type": "boolean" }, - "window_start": { + "url": { + "description": "Registered endpoint URL that receives webhook event deliveries.", + "example": "https://docs.hydradb.com/phoenix", "type": "string" } }, "type": "object" }, - "search.V2Chunk": { + "webhooks.WebhookRegisterRequest": { "properties": { - "additional_metadata": { - "additionalProperties": {}, - "description": "Pydantic aliases (see VectorStoreChunk): document_metadata→additional_metadata,\ntenant_metadata→metadata. FastAPI serializes by_alias, so the wire uses the aliases.", - "example": { - "author": "ada", - "doc_version": 3 - }, - "type": "object" - }, - "chunk_content": { - "description": "Text content of this chunk.", - "example": "HydraDB supports hybrid retrieval across knowledge and memories.", - "type": "string" - }, - "chunk_uuid": { - "description": "Unique identifier for this individual chunk.", - "example": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "type": "string" - }, - "collection": { - "description": "Collection this chunk belongs to. Canonical name; mirrors the deprecated `sub_tenant_id` alias.", - "example": "team_docs", - "type": "string" - }, - "extra_context_ids": { - "description": "IDs of adjacent chunks pulled in as surrounding context.", + "event_types": { + "description": "Event types to subscribe to (e.g. `[\"indexing.status_changed\"]`).", "example": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" + "indexing.status_changed" ], "items": { "type": "string" @@ -7555,2577 +6006,123 @@ "type": "array", "uniqueItems": false }, - "id": { - "description": "Unique identifier for this resource.", - "example": "HydraDoc1234", - "type": "string" - }, - "layout": { - "description": "Layout classification for this chunk (e.g. `text`, `table`, `image`).", - "example": "text", - "type": "string" - }, - "metadata": { - "additionalProperties": {}, - "description": "Schema-backed tenant metadata attached to the source.", - "example": { - "department": "finance", - "priority": 7 - }, - "type": "object" - }, - "relevancy_score": { - "description": "Relevance score for this item against the query.", - "example": 0.87, - "type": "number" - }, - "source_last_updated_time": { - "description": "RFC3339 timestamp when the source was last modified.", - "example": "2026-07-02T12:30:00Z", - "type": "string" - }, - "source_title": { - "description": "Title of the parent source document.", - "example": "Project Phoenix Overview", - "type": "string" + "generate_signing_secret": { + "description": "Generate a signing secret as part of this request, so registering and enabling signing are one atomic operation. The secret is returned once on the response and cannot be retrieved later. Mutually exclusive with `signing_secret`.", + "example": true, + "type": "boolean" }, - "source_type": { - "description": "Type of the parent source (e.g. `file`, `slack`, `notion`).", - "example": "file", + "signing_secret": { + "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=\u003chex\u003e`, the HMAC-SHA256 of the raw request body keyed by this secret. Minimum 16 characters when you supply your own; omit it and one is generated for you. On registration, omitting this field preserves any existing secret - to disable signing, call DELETE /webhooks/indexing/signing-secret.", + "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", "type": "string" }, - "source_upload_time": { - "description": "RFC3339 timestamp when the source was ingested.", - "example": "2026-07-02T10:00:00Z", + "url": { + "description": "Endpoint URL to deliver webhook events to.", + "example": "https://docs.hydradb.com/phoenix", "type": "string" - }, - "sub_tenant_id": { - "deprecated": true, - "description": "deprecated: use collection", - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" } }, "type": "object" }, - "search.V2RetrievalResult": { + "webhooks.WebhookRegisterResponse": { "properties": { - "additional_context": { - "additionalProperties": { - "$ref": "#/components/schemas/search.V2Chunk" - }, - "deprecated": true, - "description": "deprecated: use forceful_relations", - "example": "The user is a senior engineer onboarding to the platform.", - "type": "object", - "x-deprecated": "true" - }, - "alias_expansions": { - "description": "AliasExpansions is the alias layer's honesty stamp (V0): which nickname\nwas expanded to which canonical name for this answer.", + "event_types": { + "description": "Event types to subscribe to (e.g. `[\"indexing.status_changed\"]`).", + "example": [ + "indexing.status_changed" + ], "items": { - "$ref": "#/components/schemas/search.AliasExpansionNote" + "type": "string" }, "type": "array", "uniqueItems": false }, - "app_search_fusion": { - "$ref": "#/components/schemas/search.AppSearchFusionDiagnostics", - "description": "App-search fusion diagnostics for unscoped requests: final returned chunk attribution and pre-postprocessing fusion counts. Omitted for ACL-scoped requests and when no attributed chunks remain.", - "example": { - "stats": { - "app_chunks": 1, - "app_has_exact_ids": true, - "app_lane_empty_text": true, - "consensus": 1, - "exact_candidates": 1, - "exact_promoted": 1, - "limit": 1, - "normal_chunks": 1, - "normal_displaced": 1, - "tail_added": 1, - "tail_candidates": 1 - }, - "stats_by_pass": [ - { - "app_chunks": 1, - "app_has_exact_ids": true, - "app_lane_empty_text": true, - "consensus": 1, - "exact_candidates": 1, - "exact_promoted": 1, - "limit": 1, - "normal_chunks": 1, - "normal_displaced": 1, - "tail_added": 1, - "tail_candidates": 1 - } - ] - } - }, - "chunks": { - "description": "Retrieved and ranked chunks from the knowledge store or memories.", - "example": [ - { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } - ], - "items": { - "$ref": "#/components/schemas/search.V2Chunk" - }, - "type": "array", - "uniqueItems": false - }, - "code_search": { - "$ref": "#/components/schemas/search.CodeSearchResult", - "example": { - "duration_ms": 0.5, - "repos": [ - { - "duration_ms": 0.5, - "error": "", - "status": "completed", - "truncated": true, - "unsigned": true - } - ], - "status": "completed" - } - }, - "forceful_relations": { - "$ref": "#/components/schemas/search.ForcefulRelationsBucket", - "example": { - "declared": [ - { - "chunk": { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } - } - ], - "inferred": [ - { - "chunk": { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "chunk_content": "HydraDB supports hybrid retrieval across knowledge and memories.", - "chunk_uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "collection": "team_docs", - "extra_context_ids": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "id": "HydraDoc1234", - "layout": "text", - "metadata": { - "department": "finance", - "priority": 7 - }, - "relevancy_score": 0.87, - "source_last_updated_time": "2026-07-02T12:30:00Z", - "source_title": "Project Phoenix Overview", - "source_type": "file", - "source_upload_time": "2026-07-02T10:00:00Z", - "sub_tenant_id": "sub_tenant_4567" - } - } - ] - } - }, - "graph": { - "$ref": "#/components/schemas/search.GraphPlane", - "example": { - "paths": [ - { - "chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "relevancy_score": 0.87, - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] - } - ] - } - }, - "graph_context": { - "$ref": "#/components/schemas/search.GraphContext", - "example": { - "chunk_id_to_group_ids": { - "HydraEmbeddings123_0": [ - "grp_1234" - ] - }, - "chunk_relations": [ - { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "group_id": "grp_1234", - "relevancy_score": 0.87, - "source_chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] - } - ], - "query_paths": [ - { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", - "group_id": "grp_1234", - "relevancy_score": 0.87, - "source_chunk_ids": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "triplets": [ - { - "relation": { - "confidence": 0.92, - "predicate": "works_at" - }, - "source": { - "entity_id": "entity_1a2b", - "name": "Ada", - "type": "person" - }, - "target": { - "entity_id": "entity_3c4d", - "name": "Acme Corp", - "type": "organization" - } - } - ] - } - ] - } - }, - "profile_context": { - "$ref": "#/components/schemas/search.ProfileContext", - "example": { - "entity_id": "entity_1a2b", - "entries": [ - { - "confidence": 0.92 - } - ], - "name": "general", - "version": 1 - } - }, - "profile_filter": { - "$ref": "#/components/schemas/search.ProfileFilterInfo", - "example": { - "applied": true, - "degraded": true, - "entity_id": "entity_1a2b", - "found": true, - "selected_entries": 1, - "version": 1 - } - }, - "profiles": { - "description": "Profiles are the auto-selected profiles for the query's graph-resolved\nentities (PRO-1797); additive to ProfileContext, omitted when none.", - "example": [ - { - "entity_id": "entity_1a2b", - "entries": [ - { - "confidence": 0.92 - } - ], - "name": "general", - "version": 1 - } - ], - "items": { - "$ref": "#/components/schemas/search.ProfileContext" - }, - "type": "array", - "uniqueItems": false - }, - "resolved_references": { - "description": "ResolvedReferences report description-based references the query resolved\nand expanded with (PRO-1797 Stage 2); omitted when none.", - "items": { - "$ref": "#/components/schemas/search.ResolvedReference" - }, - "type": "array", - "uniqueItems": false - }, - "source_facts": { - "description": "SourceFacts surface the matched app-native (edge_source) facts when\nsource_reasoning was active; omitted otherwise (PRO-1602).", - "example": [ - { - "app_kind": "slack", - "chunk_id": "HydraEmbeddings123_0", - "provider": "slack", - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "synced_at": 1 - } - ], - "items": { - "$ref": "#/components/schemas/search.SourceFact" - }, - "type": "array", - "uniqueItems": false - }, - "source_filter": { - "$ref": "#/components/schemas/search.SourceFilterInfo", - "example": { - "applied": true, - "degraded": true, - "matched_facts": 1, - "mode": "thinking", - "provider": "slack", - "thread_scope": true, - "truncated": true - } - }, - "sources": { - "description": "Deduplicated source-level metadata for all returned chunks.", - "example": [ - { - "additional_metadata": { - "author": "ada", - "doc_version": 3 - }, - "app_external_id": "C0123456789", - "app_kind": "slack", - "app_provider": "slack", - "collection": "team_docs", - "description": "Internal overview of the Project Phoenix rollout.", - "id": "HydraDoc1234", - "metadata": { - "department": "finance", - "priority": 7 - }, - "sub_tenant_id": "sub_tenant_4567", - "timestamp": "2026-07-02T10:00:00Z", - "title": "Project Phoenix Overview", - "type": "knowledge", - "url": "https://docs.hydradb.com/phoenix" - } - ], - "items": { - "$ref": "#/components/schemas/search.SourceInfo" - }, - "type": "array", - "uniqueItems": false - }, - "temporal_duration": { - "$ref": "#/components/schemas/search.TemporalDuration", - "example": { - "approximate": true, - "days": 1, - "from": { - "chunk_id": "HydraEmbeddings123_0", - "event_end": 1, - "event_start": 1, - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "status": "completed" - }, - "pairing_confidence": 0.5, - "to": { - "chunk_id": "HydraEmbeddings123_0", - "event_end": 1, - "event_start": 1, - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "status": "completed" - } - } - }, - "temporal_facts": { - "description": "TemporalFacts surface the matched edge-level temporal facts when\ntemporal_reasoning was requested; omitted otherwise.", - "example": [ - { - "chunk_id": "HydraEmbeddings123_0", - "event_end": 1, - "event_start": 1, - "relationship_id": "rel_1234", - "source_id": "HydraDoc1234", - "status": "completed" - } - ], - "items": { - "$ref": "#/components/schemas/search.TemporalFact" - }, - "type": "array", - "uniqueItems": false - }, - "temporal_filter": { - "$ref": "#/components/schemas/search.TemporalFilterInfo", - "example": { - "applied": true, - "chunk_scope": 1, - "degraded": true, - "matched_facts": 1, - "mode": "thinking", - "promoted": 1, - "truncated": true - } - } - }, - "type": "object" - }, - "search.VectorStoreChunk": { - "properties": { - "additional_metadata": { - "additionalProperties": {}, - "description": "Pydantic aliases: document_metadata→additional_metadata, tenant_metadata→metadata.\nFastAPI serializes responses by_alias, so the wire uses the alias names.", - "example": { - "author": "ada", - "doc_version": 3 - }, - "type": "object" - }, - "chunk_content": { - "description": "Text content of this chunk.", - "example": "HydraDB supports hybrid retrieval across knowledge and memories.", - "type": "string" - }, - "chunk_uuid": { - "description": "Unique identifier for this individual chunk.", - "example": "a1b2c3d4-e5f6-7890-1234-567890abcdef", - "type": "string" - }, - "extra_context_ids": { - "description": "IDs of adjacent chunks pulled in as surrounding context.", - "example": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "layout": { - "description": "Layout classification for this chunk (e.g. `text`, `table`, `image`).", - "example": "text", - "type": "string" - }, - "metadata": { - "additionalProperties": {}, - "example": { - "department": "finance", - "priority": 7 - }, - "type": "object" - }, - "relevancy_score": { - "description": "Relevance score for this item against the query.", - "example": 0.87, - "type": "number" - }, - "source_id": { - "example": "HydraDoc1234", - "type": "string" - }, - "source_last_updated_time": { - "description": "RFC3339 timestamp when the source was last modified.", - "example": "2026-07-02T12:30:00Z", - "type": "string" - }, - "source_title": { - "description": "Title of the parent source document.", - "example": "Project Phoenix Overview", - "type": "string" - }, - "source_type": { - "description": "Type of the parent source (e.g. `file`, `slack`, `notion`).", - "example": "file", - "type": "string" - }, - "source_upload_time": { - "description": "RFC3339 timestamp when the source was ingested.", - "example": "2026-07-02T10:00:00Z", - "type": "string" - }, - "sub_tenant_id": { - "example": "sub_tenant_4567", - "type": "string" - } - }, - "type": "object" - }, - "sources.MemoryDeleteResponse": { - "properties": { - "deleted_count": { - "description": "Total number of items successfully deleted.", - "example": 1, - "type": "integer" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "results": { - "description": "Per-item results.", - "example": [ - { - "deleted": true, - "error": "", - "id": "HydraDoc1234" - } - ], - "items": { - "$ref": "#/components/schemas/sources.SourceDeleteResultItem" - }, - "type": "array", - "uniqueItems": false - }, - "success": { - "deprecated": true, - "description": "Deprecated for API clients: whether the REQUEST succeeded is the HTTP\nstatus code, or equivalently the envelope's top-level `success`. Whether\nanything was actually removed is deleted_count (0 means the ids matched\nnothing) and per-id results[].deleted / results[].error. This flag is\ntrue even for a delete that removed nothing, so it cannot answer either\nquestion on its own. Still emitted unchanged for existing clients\n(PRO-1208).", - "example": true, - "type": "boolean", - "x-deprecated": "true" - }, - "user_memory_deleted": { - "description": "Number of memory items deleted.", - "example": 1, - "type": "integer" - } - }, - "type": "object" - }, - "sources.SourceDeleteResultItem": { - "properties": { - "deleted": { - "description": "Whether this specific item was deleted.", - "example": true, - "type": "boolean" - }, - "error": { - "description": "Error message for this item, empty string on success.", - "example": "", - "type": "string" - }, - "id": { - "description": "Unique identifier for this resource.", - "example": "HydraDoc1234", - "type": "string" - } - }, - "type": "object" - }, - "sources.V2SourceDeleteRequest": { - "properties": { - "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", - "example": "team_docs", - "type": "string" - }, - "database": { - "description": "Database/Collection are the canonical v2 names; TenantID/SubTenantID are\ntheir deprecated aliases, reconciled by the TenantAliases middleware before\nbinding so TenantID is always populated.", - "example": "acme_corp", - "type": "string" - }, - "ids": { - "description": "IDs of the sources or memories to delete.", - "example": [ - "HydraDoc1234", - "HydraDoc4567" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "sub_tenant_id": { - "deprecated": true, - "description": "deprecated: use collection", - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - }, - "tenant_id": { - "deprecated": true, - "description": "deprecated: use database", - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - }, - "type": { - "deprecated": true, - "description": "Deprecated: kept for split databases.\nType names the corpus: knowledge (default) or memory.", - "enum": [ - "knowledge", - "memory", - "all" - ], - "example": "knowledge", - "type": "string", - "x-deprecated": true - } - }, - "type": "object" - }, - "tenants.CollectionStats": { - "properties": { - "row_count": { - "description": "Total number of indexed rows in this collection.", - "example": 1280, - "type": "integer" - } - }, - "type": "object" - }, - "tenants.CustomPropertyDefinition": { - "properties": { - "data_type": { - "$ref": "#/components/schemas/tenants.MilvusDataType", - "description": "Milvus data type for this metadata field.", - "example": "VARCHAR" - }, - "enable_dense_embedding": { - "description": "Whether to enable semantic (dense) embedding search on this field.", - "example": true, - "type": "boolean" - }, - "enable_match": { - "description": "Whether to enable exact-match filtering on this field.", - "example": true, - "type": "boolean" - }, - "enable_sparse_embedding": { - "description": "Whether to enable BM25 (sparse) embedding search on this field.", - "example": false, - "type": "boolean" - }, - "max_length": { - "description": "Maximum string length in bytes for VARCHAR fields.", - "example": 256, - "type": "integer" - }, - "name": { - "description": "Field name. Immutable after database creation.", - "example": "category", - "type": "string" - } - }, - "type": "object" - }, - "tenants.DatabaseDetail": { - "properties": { - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "type": { - "description": "Type is the storage layout the database was created with: \"split\" (a\nknowledge and a memory corpus, selected by `type` on every call).\nAbsent where the deployment does not expose the layout.", - "enum": [ - "split" - ], - "example": "split", - "type": "string" - } - }, - "type": "object" - }, - "tenants.FailedTenant": { - "properties": { - "database": { - "description": "Database identifier that failed provisioning.", - "example": "acme_corp", - "type": "string" - }, - "error": { - "description": "Error message explaining why the database failed.", - "example": "", - "type": "string" - }, - "tenant_id": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.InfraStatusResponseV2": { - "properties": { - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "infra": { - "$ref": "#/components/schemas/tenants.InfraV2", - "example": { - "graph_status": true, - "ready_for_ingestion": true, - "scheduler_status": true, - "vectorstore_status": { - "knowledge": true, - "memories": true - } - } - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "org_id": { - "description": "Organization that owns this resource.", - "example": "org_1a2b3c", - "type": "string" - }, - "tenant_id": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - }, - "type": { - "description": "Type is the storage layout the database was created with; absent while the\ndatabase is deleting or unknown.", - "enum": [ - "split" - ], - "example": "split", - "type": "string" - } - }, - "type": "object" - }, - "tenants.InfraV2": { - "properties": { - "graph_status": { - "description": "Whether the graph store is healthy for this database.", - "example": true, - "type": "boolean" - }, - "ready_for_ingestion": { - "description": "Derived readiness flag: true only when scheduler_status (lifecycle provisioning finished), graph_status, and both vectorstore_status.knowledge and vectorstore_status.memories are true — i.e. the database is fully provisioned and ready to accept ingestion and serve queries. Database creation is asynchronous: collections may appear before provisioning completes, so poll GET /databases/status until this is true before ingesting or querying.", - "example": true, - "type": "boolean" - }, - "scheduler_status": { - "description": "Whether lifecycle provisioning has finished for this database (creation_status is ready). False while the database is still being created, even if individual collections already exist.", - "example": true, - "type": "boolean" - }, - "vectorstore_status": { - "$ref": "#/components/schemas/tenants.VectorstoreStatusV2", - "example": { - "knowledge": true, - "memories": true - } - } - }, - "type": "object" - }, - "tenants.MilvusDataType": { - "description": "Declared type of a database metadata schema field. ARRAY appears in this enum because schemas persisted before it was rejected still read back and rebuild, but it CANNOT be declared on a new or evolved field: both the create and the update-metadata-schema endpoints answer 400 for it. For a field holding several values, declare VARCHAR and store the values comma-joined, then filter one member with the contains operator.", - "enum": [ - "BOOL", - "INT8", - "INT16", - "INT32", - "INT64", - "FLOAT", - "DOUBLE", - "VARCHAR", - "JSON", - "ARRAY" - ], - "type": "string", - "x-enum-varnames": [ - "DataTypeBool", - "DataTypeInt8", - "DataTypeInt16", - "DataTypeInt32", - "DataTypeInt64", - "DataTypeFloat", - "DataTypeDouble", - "DataTypeVarchar", - "DataTypeJSON", - "DataTypeArray" - ] - }, - "tenants.SubTenantDeleteResponse": { - "properties": { - "collection": { - "description": "Collection scope. Defaults to the default collection when omitted. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated).", - "example": "team_docs", - "type": "string" - }, - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "status": { - "description": "Current lifecycle or processing state.", - "example": "completed", - "type": "string" - }, - "sub_tenant_id": { - "deprecated": true, - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - }, - "tenant_id": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.SubTenantIdsResponse": { - "properties": { - "collections": { - "description": "List of collection identifiers for this database.", - "example": [ - "team_docs", - "engineering" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "sub_tenant_ids": { - "deprecated": true, - "description": "Deprecated alias for `collections`.", - "example": [ - "sub_tenant_4567", - "sub_tenant_8901" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false, - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.TenantCreateAcceptedResponse": { - "properties": { - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "status": { - "description": "Current lifecycle or processing state.", - "example": "completed", - "type": "string" - }, - "tenant_id": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.TenantCreateRequest": { - "properties": { - "database": { - "description": "Database is the canonical v2 name; TenantID is its deprecated alias and\nremains fully accepted. The TenantAliases middleware reconciles them before\nthis binds, so TenantID is always populated.", - "example": "acme_corp", - "type": "string" - }, - "database_metadata_schema": { - "description": "Defines database-level metadata fields for exact-match filtering and semantic/BM25 search. Canonical name; `tenant_metadata_schema` is a deprecated alias. Schema field names are immutable after database creation.", - "example": [ - { - "data_type": "VARCHAR", - "enable_dense_embedding": true, - "enable_match": true, - "enable_sparse_embedding": false, - "max_length": 256, - "name": "category" - } - ], - "items": { - "$ref": "#/components/schemas/tenants.CustomPropertyDefinition" - }, - "type": "array", - "uniqueItems": false - }, - "embeddings_dimension": { - "description": "Override for the embedding vector dimension. Default: 1536.", - "example": 1536, - "type": "integer" - }, - "is_embeddings_tenant": { - "description": "Internal flag for embedding-only databases.", - "example": false, - "type": "boolean" - }, - "tenant_id": { - "deprecated": true, - "description": "deprecated: use database", - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - }, - "tenant_metadata_schema": { - "deprecated": true, - "description": "deprecated: use database_metadata_schema", - "items": { - "$ref": "#/components/schemas/tenants.CustomPropertyDefinition" - }, - "type": "array", - "uniqueItems": false, - "x-deprecated": "true" - }, - "type": { - "$ref": "#/components/schemas/github_com_hydradb_hydradb-application_internal_platform_storagelayout.Layout" - } - }, - "type": "object" - }, - "tenants.TenantDeleteResponse": { - "properties": { - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "status": { - "description": "Current lifecycle or processing state.", - "example": "completed", - "type": "string" - }, - "tenant_id": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.TenantIdsResponse": { - "properties": { - "databases": { - "description": "List of database identifiers.", - "example": [ - "acme_corp", - "research_kb" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "details": { - "description": "Details carries one entry per live database with its storage layout.", - "example": [ - { - "database": "acme_corp", - "type": "split" - } - ], - "items": { - "$ref": "#/components/schemas/tenants.DatabaseDetail" - }, - "type": "array", - "uniqueItems": false - }, - "failed_databases": { - "description": "Databases that failed provisioning, with error details.", - "example": [ - { - "database": "acme_corp", - "error": "", - "tenant_id": "tenant_1234" - } - ], - "items": { - "$ref": "#/components/schemas/tenants.FailedTenant" - }, - "type": "array", - "uniqueItems": false - }, - "failed_tenant_ids": { - "deprecated": true, - "description": "Deprecated alias for `failed_databases`.", - "example": [ - { - "database": "acme_corp", - "error": "", - "tenant_id": "tenant_1234" - } - ], - "items": { - "$ref": "#/components/schemas/tenants.FailedTenant" - }, - "type": "array", - "uniqueItems": false, - "x-deprecated": "true" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "tenant_ids": { - "deprecated": true, - "description": "Deprecated alias for `databases`.", - "example": [ - "tenant_1234", - "tenant_5678" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false, - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.TenantMetadataSchemaResponse": { - "properties": { - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "fields": { - "example": [ - { - "data_type": "VARCHAR", - "enable_dense_embedding": true, - "enable_match": true, - "enable_sparse_embedding": false, - "max_length": 256, - "name": "category" - } - ], - "items": { - "$ref": "#/components/schemas/tenants.CustomPropertyDefinition" - }, - "type": "array", - "uniqueItems": false - }, - "tenant_id": { - "deprecated": true, - "example": "acme_corp", - "type": "string", - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.TenantMetadataSchemaUpdateRequest": { - "properties": { - "add_fields": { - "description": "New metadata schema fields to add to the database. Additive only — no deletes, renames, or type changes.", - "example": [ - { - "data_type": "VARCHAR", - "enable_dense_embedding": true, - "enable_match": true, - "enable_sparse_embedding": false, - "max_length": 256, - "name": "category" - } - ], - "items": { - "$ref": "#/components/schemas/tenants.CustomPropertyDefinition" - }, - "type": "array", - "uniqueItems": false - } - }, - "type": "object" - }, - "tenants.TenantRenameRequest": { - "properties": { - "new_name": { - "description": "NewName is the database's new caller-facing name. Same rules as a\ncreate-time name (ValidateTenantName).\n\nbinding:\"required\" is read by swag, so the published schema lists the\nfield as required and generated SDKs make it a mandatory argument; it is\ninert at runtime, because the handler decodes through ParseRenameRequest,\nwhich rejects a missing or blank new_name itself.", - "type": "string" - } - }, - "required": [ - "new_name" - ], - "type": "object" - }, - "tenants.TenantRenameResponse": { - "properties": { - "connector_reassignment": { - "description": "ConnectorReassignment reports how the rename's connector sweep ended:\n\"complete\" (every connector already targets the new name), \"queued\" (a\ndurable background reconciliation owns the remainder and retries until\ndrained), or \"failed\" (neither — contact support; the failure is also\nalerted on server-side). The rename itself has succeeded in all three\nstates.", - "example": "complete", - "type": "string" - }, - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "old_database": { - "type": "string" - }, - "old_tenant_id": { - "deprecated": true, - "type": "string", - "x-deprecated": "true" - }, - "status": { - "description": "Current lifecycle or processing state.", - "example": "completed", - "type": "string" - }, - "tenant_id": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.TenantStatsResponse": { - "properties": { - "database": { - "description": "Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated).", - "example": "acme_corp", - "type": "string" - }, - "knowledge_collection": { - "$ref": "#/components/schemas/tenants.CollectionStats", - "example": { - "row_count": 1280 - } - }, - "memory_collection": { - "$ref": "#/components/schemas/tenants.CollectionStats", - "example": { - "row_count": 1280 - } - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "tenant_id": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - "type": "object" - }, - "tenants.VectorstoreStatusV2": { - "properties": { - "knowledge": { - "description": "Whether the knowledge vector store is healthy.", - "example": true, - "type": "boolean" - }, - "memories": { - "description": "Whether the memories vector store is healthy.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, - "webhooks.DeliveryItem": { - "properties": { - "attempts": { - "description": "Number of delivery attempts made.", - "example": 1, - "type": "integer" - }, - "created_at": { - "description": "RFC3339 timestamp when this item was created.", - "example": "2026-07-02T10:00:00Z", - "type": "string" - }, - "delivery_id": { - "description": "Unique identifier for this webhook delivery attempt.", - "example": "dlv_9f8e7d6c", - "type": "string" - }, - "doc_id": { - "description": "Source ID that triggered this delivery.", - "example": "HydraDoc1234", - "type": "string" - }, - "error_code": { - "description": "Machine-readable error code, empty string on success.", - "example": "", - "type": "string" - }, - "error_message": { - "description": "Human-readable error description, empty string on success.", - "example": "", - "type": "string" - }, - "event_type": { - "description": "Event that triggered this delivery (e.g. `indexing.status_changed`).", - "example": "indexing.status_changed", - "type": "string" - }, - "indexing_status": { - "description": "Current processing state: `queued`, `processing`, `completed`, or `failed`.", - "example": "completed", - "type": "string" - }, - "status": { - "description": "Current delivery status (e.g. `completed`, `failed`, `permanently_failed`).", - "example": "completed", - "type": "string" - }, - "updated_at": { - "description": "RFC3339 timestamp of the most recent update.", - "example": "2026-07-02T10:00:05Z", - "type": "string" - }, - "webhook_url": { - "description": "Endpoint this delivery was aimed at. Attributes history to the endpoint\nthat was registered when the delivery was created, rather than to whatever\nis registered now, so a changed URL does not inherit the old one's failures.", - "type": "string" - } - }, - "type": "object" - }, - "webhooks.DeliveryListResponse": { - "properties": { - "count": { - "description": "Total number of items returned.", - "example": 12, - "type": "integer" - }, - "deliveries": { - "description": "List of webhook delivery attempt records.", - "example": [ - { - "attempts": 1, - "created_at": "2026-07-02T10:00:00Z", - "delivery_id": "dlv_9f8e7d6c", - "doc_id": "HydraDoc1234", - "error_code": "", - "error_message": "", - "event_type": "indexing.status_changed", - "indexing_status": "completed", - "status": "completed", - "updated_at": "2026-07-02T10:00:05Z" - } - ], - "items": { - "$ref": "#/components/schemas/webhooks.DeliveryItem" - }, - "type": "array", - "uniqueItems": false - }, - "next_cursor": { - "description": "Opaque pagination cursor for the next page; null or absent when no more pages.", - "example": "eyJvZmZzZXQiOjUwfQ==", - "type": "string" - } - }, - "type": "object" - }, - "webhooks.RetryResponse": { - "properties": { - "delivery_id": { - "description": "Unique identifier for this webhook delivery attempt.", - "example": "dlv_9f8e7d6c", - "type": "string" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "queued": { - "description": "Whether the retry was successfully queued.", - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, - "webhooks.SigningSecretRequest": { - "properties": { - "signing_secret": { - "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=\u003chex\u003e`, the HMAC-SHA256 of the raw request body keyed by this secret. Minimum 16 characters when you supply your own; omit it and one is generated for you. On registration, omitting this field preserves any existing secret - to disable signing, call DELETE /webhooks/indexing/signing-secret.", - "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", - "type": "string" - } - }, - "type": "object" - }, - "webhooks.SigningSecretResponse": { - "properties": { - "generated": { - "description": "Whether the returned secret was generated by HydraDB rather than supplied by you.", - "example": true, - "type": "boolean" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "signing_secret": { - "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=\u003chex\u003e`, the HMAC-SHA256 of the raw request body keyed by this secret. Minimum 16 characters when you supply your own; omit it and one is generated for you. On registration, omitting this field preserves any existing secret - to disable signing, call DELETE /webhooks/indexing/signing-secret.", - "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", - "type": "string" - } - }, - "type": "object" - }, - "webhooks.WebhookDeleteResponse": { - "properties": { - "deleted": { - "description": "Whether this specific item was deleted.", - "example": true, - "type": "boolean" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - } - }, - "type": "object" - }, - "webhooks.WebhookGetResponse": { - "properties": { - "event_types": { - "description": "Event types to subscribe to (e.g. `[\"indexing.status_changed\"]`).", - "example": [ - "indexing.status_changed" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "registered": { - "description": "Whether a webhook is registered for this API key.", - "example": true, - "type": "boolean" - }, - "signing_secret_configured": { - "description": "Whether a signing secret has been configured for payload verification.", - "example": true, - "type": "boolean" - }, - "url": { - "description": "Registered endpoint URL that receives webhook event deliveries.", - "example": "https://docs.hydradb.com/phoenix", - "type": "string" - } - }, - "type": "object" - }, - "webhooks.WebhookRegisterRequest": { - "properties": { - "event_types": { - "description": "Event types to subscribe to (e.g. `[\"indexing.status_changed\"]`).", - "example": [ - "indexing.status_changed" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "generate_signing_secret": { - "description": "Generate a signing secret as part of this request, so registering and enabling signing are one atomic operation. The secret is returned once on the response and cannot be retrieved later. Mutually exclusive with `signing_secret`.", - "example": true, - "type": "boolean" - }, - "signing_secret": { - "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=\u003chex\u003e`, the HMAC-SHA256 of the raw request body keyed by this secret. Minimum 16 characters when you supply your own; omit it and one is generated for you. On registration, omitting this field preserves any existing secret - to disable signing, call DELETE /webhooks/indexing/signing-secret.", - "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", - "type": "string" - }, - "url": { - "description": "Endpoint URL to deliver webhook events to.", - "example": "https://docs.hydradb.com/phoenix", - "type": "string" - } - }, - "type": "object" - }, - "webhooks.WebhookRegisterResponse": { - "properties": { - "event_types": { - "description": "Event types to subscribe to (e.g. `[\"indexing.status_changed\"]`).", - "example": [ - "indexing.status_changed" - ], - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "registered": { - "description": "Whether a webhook is registered for this API key.", - "example": true, - "type": "boolean" - }, - "signing_secret": { - "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=\u003chex\u003e`, the HMAC-SHA256 of the raw request body keyed by this secret. Minimum 16 characters when you supply your own; omit it and one is generated for you. On registration, omitting this field preserves any existing secret - to disable signing, call DELETE /webhooks/indexing/signing-secret.", - "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", - "type": "string" - }, - "signing_secret_configured": { - "description": "Whether a signing secret has been configured for payload verification.", - "example": true, - "type": "boolean" - }, - "url": { - "description": "Registered endpoint URL that receives webhook event deliveries.", - "example": "https://docs.hydradb.com/phoenix", - "type": "string" - } - }, - "type": "object" - }, - "webhooks.WebhookTestResponse": { - "properties": { - "delivered": { - "description": "Whether the test delivery was accepted by the endpoint.", - "example": true, - "type": "boolean" - }, - "message": { - "description": "Human-readable result message.", - "example": "Success", - "type": "string" - }, - "status_code": { - "description": "HTTP status code returned by the webhook endpoint.", - "example": 200, - "type": "integer" - } - }, - "type": "object" - } - }, - "securitySchemes": { - "BearerAuth": { - "bearerFormat": "API key", - "description": "API key sent as a Bearer token: \"Bearer prefix.secret\"", - "scheme": "bearer", - "type": "http" - } - } - }, - "externalDocs": { - "description": "", - "url": "" - }, - "info": { - "contact": { - "email": "support@hydradb.com", - "name": "HydraDB Support" - }, - "description": "HydraDB Application API — knowledge ingestion, search, and memory management.", - "license": { - "name": "Proprietary" - }, - "title": "HydraDB Application API", - "version": "0.1.0" - }, - "openapi": "3.1.0", - "paths": { - "/connector-catalog": { - "get": { - "description": "List every provider in the supported_connectors control-plane table (availability, sync engine, maturity, category) for the dashboard connector catalog.", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorCatalogResponse" - } - } - }, - "description": "OK" - } - }, - "summary": "List the connector catalog", - "tags": [ - "connectors" - ], - "x-fern-sdk-method-name": "catalog" - } - }, - "/connector-discovery": { - "post": { - "description": "List a provider's resources directly from supplied credentials, before creating a connector. Passing cursor or limit opts into pagination (currently Notion only): the response then adds next_cursor and has_more, a page may hold fewer than limit resources, and clients must continue while has_more is true. Without either param the full resource list is returned.", - "parameters": [ - { - "description": "Opaque pagination cursor from a previous response's next_cursor", - "in": "query", - "name": "cursor", - "schema": { - "type": "string" - } - }, - { - "description": "Max resources per page, 1-100 (values above 100 are clamped)", - "in": "query", - "name": "limit", - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.discoverPreviewReq" - } - } - }, - "description": "Provider and credentials", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.discoverResponseBody" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "502": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Gateway" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Preview provider resources", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "discover_preview" - } - }, - "/connectors": { - "get": { - "description": "List all connectors for the authenticated org, optionally filtered by provider.", - "parameters": [ - { - "description": "Filter by provider", - "in": "query", - "name": "provider", - "schema": { - "example": "slack", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorListResponse" - } - } - }, - "description": "OK" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "List connectors", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "list" - }, - "post": { - "description": "Create a connector for a provider and store its credentials.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorCreateReq" - } - } - }, - "description": "Connector configuration", - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorCreateResponse" - } - } - }, - "description": "Created" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Create a connector", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "create" - } - }, - "/connectors/providers": { - "get": { - "description": "Without ?id: returns every supported connector with its availability, maturity, category, and sync engine (the connector catalog). With ?id=\u003cprovider\u003e: returns what that provider stores and how to use it — indexed_object_types (the streams that become searchable documents), searchable_fields (rendered into the indexed text), filterable_fields (each with the exact filter_key to pass in a query's metadata_filters), the credential_schema for connecting it, and setup_guide (present for providers whose configuration goes beyond the credential schema — e.g. bigquery's per-table cursor/change-history settings and the one-time ALTER statement they may require).", - "parameters": [ - { - "description": "Provider name (e.g. slack, gmail). Omit to list all.", - "in": "query", - "name": "id", - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.providerListResponse" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - } - }, - "summary": "List supported providers, or describe one in detail", - "tags": [ - "connectors" - ], - "x-fern-sdk-method-name": "listProviders" - } - }, - "/connectors/{id}": { - "delete": { - "description": "Delete a connector, its resources, and stored credentials.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorDeleteResponse" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Delete a connector", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "delete" - }, - "get": { - "description": "Fetch a single connector by ID.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorAPIView" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Get a connector", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "get" - }, - "patch": { - "description": "Update a connector's mutable settings. `sync_interval_seconds` sets the cadence at which the scheduler starts incremental syncs: the allowed range is provider-aware and returned in the response; values outside it are rejected rather than clamped; 0 resets to the provider default; changing it re-anchors the next sync so a shorter cadence takes effect immediately. `credentials` reconnects the connector in place: send the provider's full credential set (what create accepts); supplied keys replace their stored values, other stored keys survive, the bundle is re-validated against the provider's credential schema, and a pending needs-reauth flag is cleared — the connector keeps its id, resources, and sync cursors. `custom_instructions` replaces the free-text steering applied when this connector's documents are ingested (an explicit empty string clears it); the change applies from the next sync cycle and does not re-process already-ingested documents. When several fields are supplied together they are validated up front and applied atomically: an invalid value rejects the whole request with nothing changed.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorUpdateReq" - } - } - }, - "description": "Connector update request", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorUpdateResponse" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Update a connector", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "update" - } - }, - "/connectors/{id}/configure": { - "post": { - "description": "Save the selected resources for a connector and trigger initial sync/backfill.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.configureReq" - } - } - }, - "description": "Resource selection and sync options", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.configureResponse" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Configure connector resources", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "configure" - } - }, - "/connectors/{id}/credentials": { - "patch": { - "description": "Internal endpoint. It is not callable with a customer API key and always returns 403 for external callers. Persists a rotated refresh_token for OAuth-bundle connectors: providers that rotate the refresh token on each exchange invalidate the previously stored one, so the new token must be written back or the next sync fails with invalid_grant. Only refresh_token is merged onto the current stored credential bundle and re-encrypted under the connector's identity context; credentials are never returned. It has no automated caller and is disabled by default: until an operator enables it, every call returns 500.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.credentialsUpdateResponse" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Rotate a connector's stored OAuth refresh token (internal use only)", - "tags": [ - "connectors" - ] - } - }, - "/connectors/{id}/discover": { - "get": { - "description": "List a connected provider's resources using the connector's stored credentials. Passing cursor or limit opts into pagination (currently Notion only): the response then adds next_cursor and has_more, a page may hold fewer than limit resources, and clients must continue while has_more is true. Without either param the full resource list is returned.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - }, - { - "description": "Opaque pagination cursor from a previous response's next_cursor", - "in": "query", - "name": "cursor", - "schema": { - "type": "string" - } - }, - { - "description": "Max resources per page, 1-100 (values above 100 are clamped)", - "in": "query", - "name": "limit", - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.discoverResponseBody" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - }, - "502": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Gateway" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Discover connector resources", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "discover" - } - }, - "/connectors/{id}/pause": { - "post": { - "description": "Stop scheduling syncs and backfills for a connector until it is resumed. A sync already running is allowed to finish. Cursors are preserved, so resuming continues from where each resource left off.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorPauseResponse" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Conflict" + "registered": { + "description": "Whether a webhook is registered for this API key.", + "example": true, + "type": "boolean" }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Pause a connector", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "pause" - } - }, - "/connectors/{id}/resources": { - "get": { - "description": "List the configured resources for a connector.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorResourcesResponse" - } - } - }, - "description": "OK" + "signing_secret": { + "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=\u003chex\u003e`, the HMAC-SHA256 of the raw request body keyed by this secret. Minimum 16 characters when you supply your own; omit it and one is generated for you. On registration, omitting this field preserves any existing secret - to disable signing, call DELETE /webhooks/indexing/signing-secret.", + "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", + "type": "string" }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" + "signing_secret_configured": { + "description": "Whether a signing secret has been configured for payload verification.", + "example": true, + "type": "boolean" }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" + "url": { + "description": "Registered endpoint URL that receives webhook event deliveries.", + "example": "https://docs.hydradb.com/phoenix", + "type": "string" } }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "List connector resources", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "list_resources" + "type": "object" }, - "post": { - "description": "Add a resource mapping to a connector.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.resourceCreateReq" - } - } - }, - "description": "Resource configuration", - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/connectors.Resource" - } - } - }, - "description": "Created" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" + "webhooks.WebhookTestResponse": { + "properties": { + "delivered": { + "description": "Whether the test delivery was accepted by the endpoint.", + "example": true, + "type": "boolean" }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" + "message": { + "description": "Human-readable result message.", + "example": "Success", + "type": "string" }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" + "status_code": { + "description": "HTTP status code returned by the webhook endpoint.", + "example": 200, + "type": "integer" } }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Create a connector resource", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "create_resource" + "type": "object" } }, - "/connectors/{id}/resources/{resource_id}": { - "delete": { - "description": "Remove a resource mapping from a connector.", + "securitySchemes": { + "BearerAuth": { + "bearerFormat": "API key", + "description": "API key sent as a Bearer token: \"Bearer prefix.secret\"", + "scheme": "bearer", + "type": "http" + } + } + }, + "externalDocs": { + "description": "", + "url": "" + }, + "info": { + "contact": { + "email": "support@hydradb.com", + "name": "HydraDB Support" + }, + "description": "The HydraDB API: ingest context, query it, and manage databases, connectors and webhooks.", + "license": { + "name": "Proprietary" + }, + "title": "HydraDB Application API", + "version": "0.1.0" + }, + "openapi": "3.1.0", + "paths": { + "/connectors": { + "get": { + "description": "List all connectors for the authenticated org, optionally filtered by provider.", "parameters": [ { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - }, - { - "description": "Resource ID", - "in": "path", - "name": "resource_id", - "required": true, + "description": "Filter by provider", + "in": "query", + "name": "provider", "schema": { - "example": "C0123456789", + "example": "slack", "type": "string" } } @@ -10135,32 +6132,12 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.resourceDeleteResponse" + "$ref": "#/components/schemas/handler.connectorListResponse" } } }, "description": "OK" }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, "500": { "content": { "application/json": { @@ -10177,57 +6154,36 @@ "BearerAuth": [] } ], - "summary": "Delete a connector resource", + "summary": "List connectors", "tags": [ "connectors" ], "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "delete_resource" + "x-fern-sdk-method-name": "list" }, - "patch": { - "description": "Updates per-resource settings. `acl` sets the customer-declared ACL for one connector resource (PRO-1684): the rule is normalized (emails prefixed, __public__ dominates, explicit-empty becomes __private__), persisted on the resource, and applied to enforcement immediately via the resource's ACL row, every already-indexed document of the resource is governed by it on the next query, with no re-sync. For providers with provider-derived ACL capture enabled, the provider's own ACL takes precedence again at the next sync; the rule is the standing fallback. `custom_instructions` sets the resource-level ingestion-instructions override (max 4000 characters): when set it replaces the connector-level custom_instructions for documents synced from this resource, an explicit empty string clears the override back to inheriting the connector's value, and changes apply from the next sync cycle. At least one field must be supplied; omitted fields are left unchanged.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - }, - { - "description": "Resource ID", - "in": "path", - "name": "resource_id", - "required": true, - "schema": { - "example": "C0123456789", - "type": "string" - } - } - ], + "post": { + "description": "Create a connector for a provider and store its credentials.", "requestBody": { "content": { "application/json": { "schema": { - "type": "object" + "$ref": "#/components/schemas/handler.connectorCreateReq" } } - } + }, + "description": "Connector configuration", + "required": true }, "responses": { - "200": { + "201": { "content": { "application/json": { "schema": { - "additionalProperties": {}, - "type": "object" + "$ref": "#/components/schemas/handler.connectorCreateResponse" } } }, - "description": "OK" + "description": "Created" }, "400": { "content": { @@ -10239,16 +6195,6 @@ }, "description": "Bad Request" }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, "500": { "content": { "application/json": { @@ -10258,68 +6204,8 @@ } }, "description": "Internal Server Error" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Update a resource's ACL rule or custom instructions", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "update_resource_acl" - } - }, - "/connectors/{id}/resume": { - "post": { - "description": "Return a paused connector to the schedule and make it due immediately. Each resource continues from its committed cursor, so data created during the pause is collected on the next cycle rather than skipped.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorPauseResponse" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Conflict" }, - "500": { + "503": { "content": { "application/json": { "schema": { @@ -10327,7 +6213,7 @@ } } }, - "description": "Internal Server Error" + "description": "Service Unavailable" } }, "security": [ @@ -10335,23 +6221,22 @@ "BearerAuth": [] } ], - "summary": "Resume a paused connector", + "summary": "Create a connector", "tags": [ "connectors" ], "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "resume" + "x-fern-sdk-method-name": "create" } }, - "/connectors/{id}/status": { + "/connectors/providers": { "get": { - "description": "Report whether a connector is working, in one call: a rollup status (healthy, degraded, failed, checking) plus per-resource detail. `degraded` means the connector is scheduled but not fully working: at least one configured resource is failing, or the latest sync cycle failed after the resources reported (in which case `error` carries the failure and `retryable` says whether waiting can fix it). `failed` means only the user can fix it: a rejected credential, a blocked connector, or a terminal cycle failure.", + "description": "Without `id`: lists every provider you can connect, with its category, maturity and display order. With `id=\u003cprovider\u003e`: describes that provider: the object types that become searchable, the searchable and filterable fields (each with the `filter_key` to use in a query filter), the `credential_schema` for connecting it, and a `setup_guide` when setup needs more than credentials.", "parameters": [ { - "description": "Connector ID", - "in": "path", + "description": "Provider name (e.g. slack, gmail). Omit to list all.", + "in": "query", "name": "id", - "required": true, "schema": { "example": "HydraDoc1234", "type": "string" @@ -10363,7 +6248,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.connectorStatusResponse" + "$ref": "#/components/schemas/handler.providerListResponse" } } }, @@ -10378,34 +6263,18 @@ } }, "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" } }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Get a connector's health", + "summary": "List supported providers, or describe one in detail", "tags": [ "connectors" ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "status" + "x-fern-sdk-method-name": "listProviders" } }, - "/connectors/{id}/sync": { - "post": { - "description": "Start a manual sync workflow for a connector.", + "/connectors/{id}": { + "delete": { + "description": "Delete a connector, its resources, and stored credentials.", "parameters": [ { "description": "Connector ID", @@ -10416,151 +6285,38 @@ "example": "HydraDoc1234", "type": "string" } - } - ], - "responses": { - "202": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.connectorSyncResponse" - } - } - }, - "description": "Accepted" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Conflict" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Trigger a connector sync", - "tags": [ - "connectors" - ], - "x-fern-sdk-group-name": "connectors", - "x-fern-sdk-method-name": "sync" - } - }, - "/context": { - "delete": { - "description": "Delete one or more knowledge sources or memories by ID.\n\nBy default this endpoint answers 200 for every outcome, including a delete\nthat removed nothing — check `data.deleted_count` and `data.results` rather\nthan the status code.\n\nSend `X-HydraDB-Delete-Status: strict` to opt in to honest status codes: a\ndelete that did not happen then answers 404/409/500 and never 200. This is\nthe recommended mode for new integrations. On those failures the response\n`data` still carries the same `results` / `deleted_count` payload a 200\ncarries, so per-id outcomes stay readable either way.\n\nThe default is expected to become strict in a future release, at which\npoint `X-HydraDB-Delete-Status: legacy` keeps the unconditional 200 for a\ncaller that is not ready.", - "parameters": [ - { - "description": "Selects the status behaviour for this request. `strict` opts in to honest 404/409/500 codes when the delete did not happen; `legacy` forces the unconditional 200. Omitted, the server default applies — currently `legacy`.", - "in": "header", - "name": "X-HydraDB-Delete-Status", - "schema": { - "enum": [ - "strict", - "legacy" - ], - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/sources.V2SourceDeleteRequest" - } - } - }, - "description": "Delete request", - "required": true - }, + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-sources_MemoryDeleteResponse" + "$ref": "#/components/schemas/handler.connectorDeleteResponse" } } }, "description": "OK" }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, "404": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.ErrorEnvelope-sources_MemoryDeleteResponse" - } - } - }, - "description": "Strict mode only. No source matched the given ids; `data` carries the same results/deleted_count payload a 200 carries" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorEnvelope-sources_MemoryDeleteResponse" + "$ref": "#/components/schemas/handler.ErrorResponse" } } }, - "description": "Strict mode only. Source is still indexing; retry after ingestion completes (see Retry-After). `data` carries the same results/deleted_count payload a 200 carries" + "description": "Not Found" }, "500": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.ErrorEnvelope-sources_MemoryDeleteResponse" + "$ref": "#/components/schemas/handler.ErrorResponse" } } }, - "description": "Strict mode only. A store failed to delete the source; the delete is retryable. `data` carries the same results/deleted_count payload a 200 carries" + "description": "Internal Server Error" } }, "security": [ @@ -10568,119 +6324,25 @@ "BearerAuth": [] } ], - "summary": "Delete documents or memories", + "summary": "Delete a connector", "tags": [ - "context" + "connectors" ], - "x-fern-sdk-group-name": "context", + "x-fern-sdk-group-name": "connectors", "x-fern-sdk-method-name": "delete" - } - }, - "/context/chunks": { + }, "get": { - "description": "Return the indexed chunk text for a source, or for a specific set of chunk ids (the ids a graph relation cites as its evidence). Chunk rows are read from the document store first and from the vector store for anything it does not hold. Chunks whose source the request's principals may not see are omitted, and so are chunks whose source cannot be established.", + "description": "Fetch a single connector by ID.", "parameters": [ { - "description": "Database (canonical name for the tenant scope)", - "in": "query", - "name": "database", - "required": true, - "schema": { - "example": "acme_corp", - "type": "string" - } - }, - { - "description": "Collection (canonical name for the sub-tenant scope)", - "in": "query", - "name": "collection", - "schema": { - "example": "team_docs", - "type": "string" - } - }, - { - "description": "Deprecated alias for database", - "in": "query", - "name": "tenant_id", - "schema": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Deprecated alias for collection", - "in": "query", - "name": "sub_tenant_id", - "schema": { - "deprecated": true, - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Source ID whose chunks to return. Required unless chunk_ids is given.", - "in": "query", + "description": "Connector ID", + "in": "path", "name": "id", + "required": true, "schema": { "example": "HydraDoc1234", "type": "string" } - }, - { - "description": "Chunk IDs to return. Repeated (chunk_ids=a\u0026chunk_ids=b) or comma-separated. Takes precedence over id.", - "in": "query", - "name": "chunk_ids", - "schema": { - "example": [ - "HydraEmbeddings123_0", - "HydraEmbeddings123_1" - ], - "items": { - "type": "string" - }, - "type": "array" - }, - "style": "form" - }, - { - "description": "Deprecated: kept for split databases. Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", - "in": "query", - "name": "type", - "schema": { - "deprecated": true, - "enum": [ - "knowledge", - "memory", - "all" - ], - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Max chunks to return", - "in": "query", - "name": "limit", - "schema": { - "default": 50, - "type": "integer" - } - }, - { - "description": "Principals to answer as (PRO-1684 document ACLs): only chunks whose source they may see are returned. Repeated (acl=a\u0026acl=b) or comma-separated. Omit for no ACL scoping.", - "in": "query", - "name": "acl", - "schema": { - "items": { - "type": "string" - }, - "type": "array" - }, - "style": "form" } ], "responses": { @@ -10688,13 +6350,13 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-search_ChunkInspectResult" + "$ref": "#/components/schemas/handler.connectorAPIView" } } }, "description": "OK" }, - "400": { + "404": { "content": { "application/json": { "schema": { @@ -10702,7 +6364,17 @@ } } }, - "description": "Bad Request" + "description": "Not Found" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" } }, "security": [ @@ -10710,127 +6382,50 @@ "BearerAuth": [] } ], - "summary": "Get chunk text", + "summary": "Get a connector", "tags": [ - "context" + "connectors" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "chunks" + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "get" } }, - "/context/ingest": { + "/connectors/{id}/configure": { "post": { - "description": "Ingest content for a database. `context` is the preferred shape (text or a conversation per item); the deprecated `documents`, `app_knowledge` and `memories` fields are selected by `type`. The same `context` array may also be sent as an application/json body.", + "description": "Save the selected resources for a connector and trigger initial sync/backfill.", + "parameters": [ + { + "description": "Connector ID", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "HydraDoc1234", + "type": "string" + } + } + ], "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "properties": { - "app_knowledge": { - "deprecated": true, - "description": "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:\u003cemail\u003e`, a bare email, `group:\u003cprovider\u003e:\u003cid\u003e`, `domain:\u003cdomain\u003e`, 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. Items may also carry `evidence_kind`/`evidence_subject` provenance labels (see document_metadata); an unknown kind returns 400.", - "title": "app_knowledge", - "type": "string", - "x-deprecated": "true" - }, - "collection": { - "title": "collection", - "type": "string" - }, - "context": { - "description": "JSON-encoded array of contexts -- text or a conversation per item. The same array may also be POSTed as an application/json body under `context`; that variant is not listed here so SDK generators emit this form, which carries every field.", - "title": "context", - "type": "string" - }, - "database": { - "title": "database", - "type": "string" - }, - "document_metadata": { - "deprecated": true, - "description": "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. Each item may also carry evidence labels (`evidence_kind`: one of assertion, record, said, done, third_party, inferred; `evidence_subject`: a stable handle for who the evidence is about, e.g. `user:kiran@acme.com`) declaring the content's provenance for entity understanding; an unknown kind returns 400.", - "title": "document_metadata", - "type": "string", - "x-deprecated": "true" - }, - "documents": { - "deprecated": true, - "items": { - "format": "binary", - "type": "string" - }, - "title": "documents", - "type": "array", - "x-deprecated": "true" - }, - "enrich": { - "default": "true", - "title": "enrich", - "type": "string" - }, - "graph_payload": { - "title": "graph_payload", - "type": "string" - }, - "instructions": { - "title": "instructions", - "type": "string" - }, - "memories": { - "deprecated": true, - "description": "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. Items may also carry `evidence_kind`/`evidence_subject` provenance labels (see document_metadata); an unknown kind returns 400.", - "title": "memories", - "type": "string", - "x-deprecated": "true" - }, - "sub_tenant_id": { - "deprecated": true, - "title": "sub_tenant_id", - "type": "string", - "x-deprecated": "true" - }, - "tenant_id": { - "deprecated": true, - "title": "tenant_id", - "type": "string", - "x-deprecated": "true" - }, - "type": { - "deprecated": true, - "enum": [ - "knowledge", - "memory" - ], - "title": "type", - "type": "string", - "x-deprecated": "true" - }, - "upsert": { - "default": "true", - "title": "upsert", - "type": "string" - } - }, - "required": [ - "database" - ], - "type": "object" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.configureReq" } } }, - "description": "Context[] body: the application/json alternative to this form. | Deprecated: kept for split databases. Corpus to write to: 'knowledge' (default) or 'memory'. 'all' is refused here: an ingest must name the one corpus it writes to. | 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) | Deprecated: knowledge files to ingest (repeatable; type=knowledge, split databases only) | Deprecated: per-document metadata as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Deprecated: app-knowledge items as a JSON array (type=knowledge, split databases only). Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB, optional acl principal list (PRO-1684). | Deprecated: memory items as a JSON array (type=memory, split databases only); use context. Per item: metadata \u003c= 16 KiB, additional_metadata \u003c= 1 KiB. | Contexts as a JSON array, the same list a JSON body carries under `context`. Each is one of text | conversation ([{role, content}]), with optional context_id, title (\u003c= 1024 bytes), user_name, enrich, upsert, instructions (\u003c= 4000 chars), happened_at, attributes, custom_attributes, context_category (auto|user_preference|business_knowledge|decision_trace), forceful_relations ({context_ids, properties}), acl. At most 100 contexts, 1 MiB of text per context and 8 MiB per request. Unknown keys are refused. Contexts land in the memory corpus. | Request-level enrichment default for `context` (true/false/1/0) | Request-level enrichment instructions default for `context` (\u003c= 4000 chars) | Optional bring-your-own-graph payload as JSON, keyed by context_id (context) or source_id (split paths)", + "description": "Resource selection and sync options", "required": true }, "responses": { - "202": { + "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-ingestion_V2IngestResponse" + "$ref": "#/components/schemas/handler.configureResponse" } } }, - "description": "Accepted" + "description": "OK" }, "400": { "content": { @@ -10842,17 +6437,7 @@ }, "description": "Bad Request" }, - "413": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Request Entity Too Large" - }, - "415": { + "404": { "content": { "application/json": { "schema": { @@ -10860,9 +6445,9 @@ } } }, - "description": "Body is neither multipart/form-data nor application/json" + "description": "Not Found" }, - "422": { + "500": { "content": { "application/json": { "schema": { @@ -10870,7 +6455,7 @@ } } }, - "description": "Unprocessable Entity" + "description": "Internal Server Error" } }, "security": [ @@ -10878,21 +6463,21 @@ "BearerAuth": [] } ], - "summary": "Ingest content", + "summary": "Configure connector resources", "tags": [ - "context" + "connectors" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "ingest" + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "configure" } }, - "/context/inspect": { + "/connectors/{id}/discover": { "get": { - "description": "Fetch a previously ingested source's content, inferred content, and a downloadable URL.", + "description": "List a connected provider's resources using the connector's stored credentials. Passing cursor or limit opts into pagination (currently Notion only): the response then adds next_cursor and has_more, a page may hold fewer than limit resources, and clients must continue while has_more is true. Without either param the full resource list is returned.", "parameters": [ { - "description": "Source ID", - "in": "query", + "description": "Connector ID", + "in": "path", "name": "id", "required": true, "schema": { @@ -10901,75 +6486,20 @@ } }, { - "description": "Database (canonical name for the tenant scope)", - "in": "query", - "name": "database", - "required": true, - "schema": { - "example": "acme_corp", - "type": "string" - } - }, - { - "description": "Collection (canonical name for the sub-tenant scope)", + "description": "Opaque pagination cursor from a previous response's next_cursor", "in": "query", - "name": "collection", + "name": "cursor", "schema": { - "example": "team_docs", "type": "string" } }, { - "description": "Deprecated alias for database", - "in": "query", - "name": "tenant_id", - "schema": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Deprecated alias for collection", - "in": "query", - "name": "sub_tenant_id", - "schema": { - "deprecated": true, - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Presigned URL expiry in seconds", + "description": "Max resources per page, 1-100 (values above 100 are clamped)", "in": "query", - "name": "expiry_seconds", + "name": "limit", "schema": { - "default": 3600, "type": "integer" } - }, - { - "description": "Fetch mode", - "in": "query", - "name": "mode", - "schema": { - "example": "thinking", - "type": "string" - } - }, - { - "description": "Principals to answer as (PRO-1684 document ACLs): the source must be visible to them, or the response is 404. Repeated (acl=a\u0026acl=b) or comma-separated. Omit for no ACL scoping.", - "in": "query", - "name": "acl", - "schema": { - "items": { - "type": "string" - }, - "type": "array" - }, - "style": "form" } ], "responses": { @@ -10977,7 +6507,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-fetch_V2SourceFetchResponse" + "$ref": "#/components/schemas/handler.discoverResponseBody" } } }, @@ -11002,6 +6532,36 @@ } }, "description": "Not Found" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Bad Gateway" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Service Unavailable" } }, "security": [ @@ -11009,40 +6569,41 @@ "BearerAuth": [] } ], - "summary": "Fetch document content", + "summary": "Discover connector resources", "tags": [ - "context" + "connectors" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "inspect" + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "discover" } }, - "/context/list": { - "post": { - "description": "List items (id + metadata) for a database: knowledge sources (default) or memories, selected by `type`.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/list.V2ListContentRequest" - } + "/connectors/{id}/resources": { + "get": { + "description": "List the configured resources for a connector.", + "parameters": [ + { + "description": "Connector ID", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "HydraDoc1234", + "type": "string" } - }, - "description": "List request", - "required": true - }, + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-list_V2ListResponse" + "$ref": "#/components/schemas/handler.connectorResourcesResponse" } } }, "description": "OK" }, - "400": { + "404": { "content": { "application/json": { "schema": { @@ -11050,7 +6611,17 @@ } } }, - "description": "Bad Request" + "description": "Not Found" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" } }, "security": [ @@ -11058,112 +6629,48 @@ "BearerAuth": [] } ], - "summary": "List documents", + "summary": "List connector resources", "tags": [ - "context" + "connectors" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "list" - } - }, - "/context/profile": { - "get": { - "description": "Return the compiled profile of one entity in one collection: identity headline, a cited summary, and the current admitted entries (each pointing at the statements behind it). Profiles are maintained continuously by the ingestion pipeline's entity keeper; this endpoint reads the materialized view and never triggers recomputation.", - "parameters": [ - { - "description": "Database (canonical name for the tenant scope)", - "in": "query", - "name": "database", - "required": true, - "schema": { - "example": "acme_corp", - "type": "string" - } - }, - { - "description": "Collection (canonical name for the sub-tenant scope)", - "in": "query", - "name": "collection", - "required": true, - "schema": { - "example": "team_docs", - "type": "string" - } - }, - { - "description": "Deprecated alias for database", - "in": "query", - "name": "tenant_id", - "schema": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Deprecated alias for collection", - "in": "query", - "name": "sub_tenant_id", - "schema": { - "deprecated": true, - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Entity whose profile to return (e.g. a person's name)", - "in": "query", - "name": "subject", - "required": true, - "schema": { - "type": "string" - } - }, - { - "description": "Corpus type: 'knowledge' or 'memory'", - "in": "query", - "name": "type", - "schema": { - "default": "memory", - "enum": [ - "knowledge", - "memory" - ], - "type": "string" - } - }, - { - "description": "Graph entity type of the subject", - "in": "query", - "name": "entity_type", - "schema": { - "default": "PERSON", - "type": "string" - } - }, + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "list_resources" + }, + "post": { + "description": "Add a resource mapping to a connector.", + "parameters": [ { - "description": "Graph namespace of the subject", - "in": "query", - "name": "namespace", + "description": "Connector ID", + "in": "path", + "name": "id", + "required": true, "schema": { - "default": "users", - "example": "organization", + "example": "HydraDoc1234", "type": "string" } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.resourceCreateReq" + } + } + }, + "description": "Resource configuration", + "required": true + }, "responses": { - "200": { + "201": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-search_EntityProfileView" + "$ref": "#/components/schemas/connectors.Resource" } } }, - "description": "OK" + "description": "Created" }, "400": { "content": { @@ -11183,7 +6690,17 @@ } } }, - "description": "No profile compiled for this subject yet, or the feature is not enabled" + "description": "Not Found" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" } }, "security": [ @@ -11191,111 +6708,37 @@ "BearerAuth": [] } ], - "summary": "Get entity profile", + "summary": "Create a connector resource", "tags": [ - "context" + "connectors" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "profile" + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "create_resource" } }, - "/context/relations": { - "get": { - "description": "Return knowledge-graph relations for a tenant or a single source.", + "/connectors/{id}/resources/{resource_id}": { + "delete": { + "description": "Remove a resource mapping from a connector.", "parameters": [ { - "description": "Database (canonical name for the tenant scope)", - "in": "query", - "name": "database", - "required": true, - "schema": { - "example": "acme_corp", - "type": "string" - } - }, - { - "description": "Collection (canonical name for the sub-tenant scope)", - "in": "query", - "name": "collection", - "schema": { - "example": "team_docs", - "type": "string" - } - }, - { - "description": "Deprecated alias for database", - "in": "query", - "name": "tenant_id", - "schema": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Deprecated alias for collection", - "in": "query", - "name": "sub_tenant_id", - "schema": { - "deprecated": true, - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Source ID (omit for database-wide relations)", - "in": "query", + "description": "Connector ID", + "in": "path", "name": "id", + "required": true, "schema": { "example": "HydraDoc1234", "type": "string" } }, { - "description": "Deprecated: kept for split databases. Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", - "in": "query", - "name": "type", - "schema": { - "deprecated": true, - "enum": [ - "knowledge", - "memory", - "all" - ], - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Max relations to return", - "in": "query", - "name": "limit", - "schema": { - "default": 5000, - "type": "integer" - } - }, - { - "description": "Pagination cursor", - "in": "query", - "name": "cursor", + "description": "Resource ID", + "in": "path", + "name": "resource_id", + "required": true, "schema": { - "type": "number" + "example": "C0123456789", + "type": "string" } - }, - { - "description": "Principals to answer as (PRO-1684 document ACLs): only relations attributable to sources they may see are returned. Repeated (acl=a\u0026acl=b) or comma-separated. Omit for no ACL scoping.", - "in": "query", - "name": "acl", - "schema": { - "items": { - "type": "string" - }, - "type": "array" - }, - "style": "form" } ], "responses": { @@ -11303,7 +6746,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-graph_GraphRelationsResponse" + "$ref": "#/components/schemas/handler.resourceDeleteResponse" } } }, @@ -11318,6 +6761,26 @@ } }, "description": "Bad Request" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" } }, "security": [ @@ -11325,96 +6788,71 @@ "BearerAuth": [] } ], - "summary": "Get graph relations", + "summary": "Delete a connector resource", "tags": [ - "context" + "connectors" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "relations" + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "delete_resource" } }, - "/context/status": { - "get": { - "description": "Return the processing status for one or more source IDs.", + "/connectors/{id}/sync": { + "post": { + "description": "Start a manual sync workflow for a connector.", "parameters": [ { - "description": "Single source ID", - "in": "query", + "description": "Connector ID", + "in": "path", "name": "id", - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - }, - { - "description": "One or more source IDs", - "in": "query", - "name": "ids", - "schema": { - "example": [ - "HydraDoc1234", - "HydraDoc4567" - ], - "items": { - "type": "string" - }, - "type": "array" - } - }, - { - "description": "Database (canonical name for the tenant scope)", - "in": "query", - "name": "database", "required": true, "schema": { - "example": "acme_corp", - "type": "string" - } - }, - { - "description": "Collection (canonical name for the sub-tenant scope)", - "in": "query", - "name": "collection", - "schema": { - "example": "team_docs", + "example": "HydraDoc1234", "type": "string" } - }, - { - "description": "Deprecated alias for database", - "in": "query", - "name": "tenant_id", - "schema": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Deprecated alias for collection", - "in": "query", - "name": "sub_tenant_id", - "schema": { - "deprecated": true, - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - } } ], "responses": { - "200": { + "202": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-ingestion_V2BatchProcessingStatus" + "$ref": "#/components/schemas/handler.connectorSyncResponse" } } }, - "description": "OK" + "description": "Accepted" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Conflict" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" }, - "400": { + "503": { "content": { "application/json": { "schema": { @@ -11422,7 +6860,7 @@ } } }, - "description": "Bad Request" + "description": "Service Unavailable" } }, "security": [ @@ -11430,125 +6868,48 @@ "BearerAuth": [] } ], - "summary": "Check processing status", + "summary": "Trigger a connector sync", "tags": [ - "context" + "connectors" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "status" + "x-fern-sdk-group-name": "connectors", + "x-fern-sdk-method-name": "sync" } }, - "/context/subgraph": { - "get": { - "description": "Query-string form of GET /context/{id}/subgraph: the same parameters, the same response, and the same rules. It exists for an id that contains '/', which cannot be spelled as one path segment; generated SDKs call this form for every id. Return the connected subgraph of one ingested item: every item reachable from it through item-level relations (explicit `relates_to` links, a shared thread, parent/child hierarchy, traversed breadth-first up to `depth` hops), the relations among those members, and the structural graph around them (entities, comments, attachments, actors). Chunk-level entity relations are not included; use Inspecting Context Relations for those. An unknown id returns an empty subgraph, not an error.", + "/context": { + "delete": { + "description": "Delete one or more contexts by ID. By default every outcome answers `200`, including a delete that removed nothing, so check `data.deleted_count` and `data.results`. Send `X-HydraDB-Delete-Status: strict` to get `404`, `409` or `500` when the delete did not happen; the body still carries the same `results` and `deleted_count`. Strict is recommended for new integrations and is expected to become the default.", "parameters": [ { - "description": "Item ID: the ingested item whose connected subgraph to return. This form takes any id, including one that contains '/'.", - "in": "query", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - }, - { - "description": "Database (canonical name for the tenant scope)", - "in": "query", - "name": "database", - "required": true, - "schema": { - "example": "acme_corp", - "type": "string" - } - }, - { - "description": "Collection (canonical name for the sub-tenant scope)", - "in": "query", - "name": "collection", - "schema": { - "example": "team_docs", - "type": "string" - } - }, - { - "description": "Deprecated alias for database", - "in": "query", - "name": "tenant_id", - "schema": { - "deprecated": true, - "example": "tenant_1234", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Deprecated alias for collection", - "in": "query", - "name": "sub_tenant_id", - "schema": { - "deprecated": true, - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Deprecated: kept for split databases. Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", - "in": "query", - "name": "type", + "description": "`strict` answers `404`, `409` or `500` when the delete did not happen; `legacy` always answers `200`. Omit it for the server default, currently `legacy`.", + "in": "header", + "name": "X-HydraDB-Delete-Status", "schema": { - "deprecated": true, "enum": [ - "knowledge", - "memory", - "all" + "strict", + "legacy" ], - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Max traversal depth in hops", - "in": "query", - "name": "depth", - "schema": { - "default": 5, - "maximum": 10, - "minimum": 1, - "type": "integer" - } - }, - { - "description": "Max members returned; `is_truncated` reports when this clipped the traversal", - "in": "query", - "name": "max_sources", - "schema": { - "default": 200, - "maximum": 1000, - "minimum": 1, - "type": "integer" + "type": "string" } - }, - { - "description": "Principals to answer as (document ACLs): the subgraph contains only items they may see, filtered at every hop. Repeated (acl=a\u0026acl=b) or comma-separated. Omit for no ACL scoping.", - "in": "query", - "name": "acl", - "schema": { - "items": { - "type": "string" - }, - "type": "array" - }, - "style": "form" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/sources.V2SourceDeleteRequest" + } + } + }, + "description": "Delete request", + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-graph_SourceSubgraphResponse" + "$ref": "#/components/schemas/handler.Envelope-sources_MemoryDeleteResponse" } } }, @@ -11563,6 +6924,36 @@ } }, "description": "Bad Request" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorEnvelope-sources_MemoryDeleteResponse" + } + } + }, + "description": "Strict mode only. No source matched the given ids; `data` carries the same results/deleted_count payload a 200 carries" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorEnvelope-sources_MemoryDeleteResponse" + } + } + }, + "description": "Strict mode only. Source is still indexing; retry after ingestion completes (see Retry-After). `data` carries the same results/deleted_count payload a 200 carries" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorEnvelope-sources_MemoryDeleteResponse" + } + } + }, + "description": "Strict mode only. A store failed to delete the source; the delete is retryable. `data` carries the same results/deleted_count payload a 200 carries" } }, "security": [ @@ -11570,50 +6961,142 @@ "BearerAuth": [] } ], - "summary": "Get connected subgraph", + "summary": "Delete documents or memories", "tags": [ "context" ], "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "subgraph" + "x-fern-sdk-method-name": "delete" } }, - "/context/{id}/metadata": { - "patch": { - "description": "Merge/upsert database_metadata and additional_metadata for one source. collection is required.", - "parameters": [ - { - "description": "Source ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], + "/context/ingest": { + "post": { + "description": "Ingest context into a database. Send a `context` list, where each context is a `text` or a `conversation`, as an application/json body or as the `context` field of a multipart form; both run the same validation. The response is `202`: the contexts are queued, not yet indexed. Poll `GET /context/status` with the returned ids.", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.contextMetadataUpdateRequest" + "$ref": "#/components/schemas/memories.ContextIngestRequest" + } + }, + "multipart/form-data": { + "schema": { + "properties": { + "app_knowledge": { + "deprecated": true, + "description": "Deprecated: send `context` instead. Kept for older databases.", + "title": "app_knowledge", + "type": "string", + "x-deprecated": "true" + }, + "collection": { + "description": "Collection to write to. Omit it to use the database's default collection.", + "title": "collection", + "type": "string" + }, + "context": { + "description": "JSON array of contexts, the same list the JSON body carries under `context`. Each context is exactly one of `text` or `conversation`. At most 100 contexts, 1 MiB of text per context and 8 MiB per request. Unknown keys are a `400`.", + "title": "context", + "type": "string" + }, + "database": { + "description": "Database to write to. Required.", + "title": "database", + "type": "string" + }, + "document_metadata": { + "deprecated": true, + "description": "Deprecated: send `context` instead. Per-file metadata for `documents` on older databases.", + "title": "document_metadata", + "type": "string", + "x-deprecated": "true" + }, + "documents": { + "deprecated": true, + "description": "Deprecated: file upload for older databases. Extract the text and send it in `context` instead.", + "items": { + "format": "binary", + "type": "string" + }, + "title": "documents", + "type": "array", + "x-deprecated": "true" + }, + "enrich": { + "default": "true", + "description": "Default `enrich` for every context: extract entities, relations and preferences into the graph. One of `true`, `false`, `1` or `0`; default `true`.", + "title": "enrich", + "type": "string" + }, + "graph_payload": { + "description": "Your own graph as a JSON string, keyed by the `context_id` of a context in this request. Same shape as `graph_payload` on the JSON body.", + "title": "graph_payload", + "type": "string" + }, + "instructions": { + "description": "Default enrichment instructions for every context that sets none. At most 4,000 characters.", + "title": "instructions", + "type": "string" + }, + "memories": { + "deprecated": true, + "description": "Deprecated: send `context` instead. Kept for older databases.", + "title": "memories", + "type": "string", + "x-deprecated": "true" + }, + "sub_tenant_id": { + "deprecated": true, + "description": "Deprecated: use `collection`.", + "title": "sub_tenant_id", + "type": "string", + "x-deprecated": "true" + }, + "tenant_id": { + "deprecated": true, + "description": "Deprecated: use `database`.", + "title": "tenant_id", + "type": "string", + "x-deprecated": "true" + }, + "type": { + "deprecated": true, + "description": "Deprecated: omit it. Kept for older databases, where it selects `knowledge` or `memory` for the deprecated form fields.", + "enum": [ + "knowledge", + "memory" + ], + "title": "type", + "type": "string", + "x-deprecated": "true" + }, + "upsert": { + "default": "true", + "description": "Default `upsert` for every context: replace an existing context with the same `context_id`. One of `true`, `false`, `1` or `0`; default `true`.", + "title": "upsert", + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" } } }, - "description": "Metadata update request", + "description": "The contexts to ingest. Send JSON with the list under `context`, or the same list as a JSON string in the `context` form field.", "required": true }, "responses": { - "200": { + "202": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-github_com_hydradb_hydradb-application_internal_service_MetadataEditResult" + "$ref": "#/components/schemas/handler.Envelope-ingestion_V2IngestResponse" } } }, - "description": "OK" + "description": "Accepted" }, "400": { "content": { @@ -11625,7 +7108,7 @@ }, "description": "Bad Request" }, - "404": { + "413": { "content": { "application/json": { "schema": { @@ -11633,9 +7116,9 @@ } } }, - "description": "Not Found" + "description": "Request Entity Too Large" }, - "500": { + "415": { "content": { "application/json": { "schema": { @@ -11643,7 +7126,17 @@ } } }, - "description": "Internal Server Error" + "description": "Body is neither multipart/form-data nor application/json" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Unprocessable Entity" } }, "security": [ @@ -11651,20 +7144,21 @@ "BearerAuth": [] } ], - "summary": "Update source metadata", + "summary": "Ingest content", "tags": [ "context" ], - "x-fern-sdk-group-name": "context" + "x-fern-sdk-group-name": "context", + "x-fern-sdk-method-name": "ingest" } }, - "/context/{id}/subgraph": { + "/context/inspect": { "get": { - "description": "Return the connected subgraph of one ingested item: every item reachable from it through item-level relations (explicit `relates_to` links, a shared thread, parent/child hierarchy, traversed breadth-first up to `depth` hops), the relations among those members, and the structural graph around them (entities, comments, attachments, actors). Chunk-level entity relations are not included; use Inspecting Context Relations for those. An unknown id returns an empty subgraph, not an error.", + "description": "Return the stored content of one context, its enrichment, and a time-limited download URL.", "parameters": [ { - "description": "Item ID: the ingested item whose connected subgraph to return. URL-encode it. An id containing a literal '/' cannot be spelled as one path segment; address those with the query form, GET /context/subgraph?id=.", - "in": "path", + "description": "The ID of the context to inspect.", + "in": "query", "name": "id", "required": true, "schema": { @@ -11673,7 +7167,7 @@ } }, { - "description": "Database (canonical name for the tenant scope)", + "description": "Database the context belongs to. Required.", "in": "query", "name": "database", "required": true, @@ -11683,7 +7177,7 @@ } }, { - "description": "Collection (canonical name for the sub-tenant scope)", + "description": "Collection the context belongs to. Omit it to use the database's default collection.", "in": "query", "name": "collection", "schema": { @@ -11692,7 +7186,8 @@ } }, { - "description": "Deprecated alias for database", + "deprecated": true, + "description": "Deprecated: use `database`.", "in": "query", "name": "tenant_id", "schema": { @@ -11703,257 +7198,46 @@ } }, { - "description": "Deprecated alias for collection", + "deprecated": true, + "description": "Deprecated: use `collection`.", "in": "query", "name": "sub_tenant_id", - "schema": { - "deprecated": true, - "example": "sub_tenant_4567", - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Deprecated: kept for split databases. Corpus type: 'knowledge' (default), 'memory', or 'all'. This read addresses one corpus, so 'all' answers from knowledge and meta.source_type reports which corpus answered.", - "in": "query", - "name": "type", - "schema": { - "deprecated": true, - "enum": [ - "knowledge", - "memory", - "all" - ], - "type": "string", - "x-deprecated": "true" - } - }, - { - "description": "Max traversal depth in hops", - "in": "query", - "name": "depth", - "schema": { - "default": 5, - "maximum": 10, - "minimum": 1, - "type": "integer" - } - }, - { - "description": "Max members returned; `is_truncated` reports when this clipped the traversal", - "in": "query", - "name": "max_sources", - "schema": { - "default": 200, - "maximum": 1000, - "minimum": 1, - "type": "integer" - } - }, - { - "description": "Principals to answer as (document ACLs): the subgraph contains only items they may see, filtered at every hop. Repeated (acl=a\u0026acl=b) or comma-separated. Omit for no ACL scoping.", - "in": "query", - "name": "acl", - "schema": { - "items": { - "type": "string" - }, - "type": "array" - }, - "style": "form" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.Envelope-graph_SourceSubgraphResponse" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Get connected subgraph", - "tags": [ - "context" - ], - "x-fern-ignore": true - } - }, - "/credential-vault": { - "get": { - "description": "List metadata and field names for credentials already used by connectors in the current workspace.", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.vaultCredentialListResponse" - } - } - }, - "description": "OK" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "List connector credentials", - "tags": [ - "connectors" - ] - } - }, - "/credential-vault/{id}": { - "patch": { - "description": "Update fields on the credential currently used by a connector. Owner-only human action.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.vaultCredentialUpdateReq" - } - } - }, - "description": "Credential fields", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.vaultCredentialUpdateResponse" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Update connector credentials", - "tags": [ - "connectors" - ] - } - }, - "/credential-vault/{id}/reveal": { - "post": { - "description": "Reveal one field from the credential currently used by a connector in the workspace. The response must never be cached.", - "parameters": [ - { - "description": "Connector ID", - "in": "path", - "name": "id", - "required": true, - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.vaultCredentialRevealReq" - } - } - }, - "description": "Credential field", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.vaultCredentialRevealResponse" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Reveal a connector credential value", - "tags": [ - "connectors" - ] - } - }, - "/databases": { - "delete": { - "description": "Delete a database and all associated data", - "parameters": [ + "schema": { + "deprecated": true, + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + } + }, { - "description": "Database identifier to delete", + "description": "Lifetime of `presigned_url` in seconds, from 60 to 604800 (7 days). Default 3600.", "in": "query", - "name": "database", - "required": true, + "name": "expiry_seconds", "schema": { - "example": "acme_corp", + "default": 3600, + "type": "integer" + } + }, + { + "description": "What to return: `content` (the stored content and enrichment), `url` (a download URL only) or `both`. Default `both`.", + "in": "query", + "name": "mode", + "schema": { + "example": "thinking", "type": "string" } + }, + { + "description": "Principals to answer as. The context must be visible to them, or the response is `404`. Repeated (`acl=a\u0026acl=b`) or comma-separated. Omit it for no access scoping.", + "in": "query", + "name": "acl", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + }, + "style": "form" } ], "responses": { @@ -11961,7 +7245,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantDeleteResponse" + "$ref": "#/components/schemas/handler.Envelope-fetch_V2SourceFetchResponse" } } }, @@ -11986,16 +7270,6 @@ } }, "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" } }, "security": [ @@ -12003,27 +7277,40 @@ "BearerAuth": [] } ], - "summary": "Delete a database", + "summary": "Fetch document content", "tags": [ - "database-management" + "context" ], - "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "delete" - }, - "get": { - "description": "List all databases for the authenticated user", + "x-fern-sdk-group-name": "context", + "x-fern-sdk-method-name": "inspect" + } + }, + "/context/list": { + "post": { + "description": "List the context in a database or collection, ingested and synced by connectors, one page at a time. Each row carries the context's `id` and its metadata; fetch its content with `GET /context/inspect`.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/list.V2ListContentRequest" + } + } + }, + "description": "List request", + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantIdsResponse" + "$ref": "#/components/schemas/handler.Envelope-list_V2ListResponse" } } }, "description": "OK" }, - "500": { + "400": { "content": { "application/json": { "schema": { @@ -12031,7 +7318,7 @@ } } }, - "description": "Internal Server Error" + "description": "Bad Request" } }, "security": [ @@ -12039,32 +7326,122 @@ "BearerAuth": [] } ], - "summary": "List databases", + "summary": "List documents", "tags": [ - "database-management" + "context" ], - "x-fern-sdk-group-name": "databases", + "x-fern-sdk-group-name": "context", "x-fern-sdk-method-name": "list" - }, - "post": { - "description": "Create a new database with optional custom metadata schema", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tenants.TenantCreateRequest" - } + } + }, + "/context/relations": { + "get": { + "description": "Return the entity relations extracted from one context, or from every context in the collection when `id` is omitted, with the structural graph around them. `limit` and `cursor` page through `relations`.", + "parameters": [ + { + "description": "Database to read. Required.", + "in": "query", + "name": "database", + "required": true, + "schema": { + "example": "acme_corp", + "type": "string" } }, - "description": "Database creation request", - "required": true - }, + { + "description": "Collection to read. Defaults to the database's default collection.", + "in": "query", + "name": "collection", + "schema": { + "example": "team_docs", + "type": "string" + } + }, + { + "deprecated": true, + "description": "Deprecated: use `database`.", + "in": "query", + "name": "tenant_id", + "schema": { + "deprecated": true, + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + } + }, + { + "deprecated": true, + "description": "Deprecated: use `collection`.", + "in": "query", + "name": "sub_tenant_id", + "schema": { + "deprecated": true, + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + } + }, + { + "description": "`context_id` of the context whose relations to return. Omit it for relations across the whole collection.", + "in": "query", + "name": "id", + "schema": { + "example": "HydraDoc1234", + "type": "string" + } + }, + { + "deprecated": true, + "description": "Deprecated: omit it. Kept for older databases. Selects the corpus to read: `knowledge` (default) or `memory`.", + "in": "query", + "name": "type", + "schema": { + "deprecated": true, + "enum": [ + "knowledge", + "memory", + "all" + ], + "type": "string", + "x-deprecated": "true" + } + }, + { + "description": "Maximum number of relation groups to return, from `1` to `10000`. Default `5000`.", + "in": "query", + "name": "limit", + "schema": { + "default": 5000, + "type": "integer" + } + }, + { + "description": "The `next_cursor` from the previous page, passed back unchanged. Omit it for the first page.", + "in": "query", + "name": "cursor", + "schema": { + "type": "number" + } + }, + { + "description": "Principals to answer as. Only results they may see are returned. Repeat the parameter (`acl=a\u0026acl=b`) or send a comma-separated list. Omit it for no access-control scoping.", + "in": "query", + "name": "acl", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + }, + "style": "form" + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantCreateAcceptedResponse" + "$ref": "#/components/schemas/handler.Envelope-graph_GraphRelationsResponse" } } }, @@ -12079,28 +7456,105 @@ } }, "description": "Bad Request" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Get graph relations", + "tags": [ + "context" + ], + "x-fern-sdk-group-name": "context", + "x-fern-sdk-method-name": "relations" + } + }, + "/context/status": { + "get": { + "description": "Return the processing status of one or more contexts by ID, including contexts synced by a connector.", + "parameters": [ + { + "description": "One context ID. Combined with `ids`.", + "in": "query", + "name": "id", + "schema": { + "example": "HydraDoc1234", + "type": "string" + } }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Forbidden" + { + "description": "Context IDs as repeated params (`ids=a\u0026ids=b`) or one comma-joined value (`ids=a,b`). Whitespace is trimmed; empty and duplicate entries are dropped.", + "in": "query", + "name": "ids", + "schema": { + "example": [ + "HydraDoc1234", + "HydraDoc4567" + ], + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Database the contexts belong to. Required.", + "in": "query", + "name": "database", + "required": true, + "schema": { + "example": "acme_corp", + "type": "string" + } + }, + { + "description": "Collection the contexts belong to. Omit it to use the database's default collection.", + "in": "query", + "name": "collection", + "schema": { + "example": "team_docs", + "type": "string" + } + }, + { + "deprecated": true, + "description": "Deprecated: use `database`.", + "in": "query", + "name": "tenant_id", + "schema": { + "deprecated": true, + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + } }, - "409": { + { + "deprecated": true, + "description": "Deprecated: use `collection`.", + "in": "query", + "name": "sub_tenant_id", + "schema": { + "deprecated": true, + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + } + } + ], + "responses": { + "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" + "$ref": "#/components/schemas/handler.Envelope-ingestion_V2BatchProcessingStatus" } } }, - "description": "Conflict" + "description": "OK" }, - "500": { + "400": { "content": { "application/json": { "schema": { @@ -12108,7 +7562,7 @@ } } }, - "description": "Internal Server Error" + "description": "Bad Request" } }, "security": [ @@ -12116,20 +7570,30 @@ "BearerAuth": [] } ], - "summary": "Create a database", + "summary": "Check processing status", "tags": [ - "database-management" + "context" ], - "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "create" + "x-fern-sdk-group-name": "context", + "x-fern-sdk-method-name": "status" } }, - "/databases/collections": { - "delete": { - "description": "Permanently remove one collection and all of its data from a database. The database itself is left intact.", + "/context/subgraph": { + "get": { + "description": "Return the connected subgraph of one context: every context reachable from it through context-level relations (declared `relates_to` links, a shared thread, parent and child), breadth-first up to `depth` hops, the relations among those members, and the structural graph around them (entities, comments, attachments, authors). Chunk-level entity relations are not included; use Inspecting Context Relations for those. An unknown id returns an empty subgraph, not an error.", "parameters": [ { - "description": "Database identifier", + "description": "The `context_id` to start from. This form takes any id, including one that contains `/`.", + "in": "query", + "name": "id", + "required": true, + "schema": { + "example": "HydraDoc1234", + "type": "string" + } + }, + { + "description": "Database to read. Required.", "in": "query", "name": "database", "required": true, @@ -12139,14 +7603,87 @@ } }, { - "description": "Collection identifier", + "description": "Collection to read. Defaults to the database's default collection.", "in": "query", "name": "collection", - "required": true, "schema": { "example": "team_docs", "type": "string" } + }, + { + "deprecated": true, + "description": "Deprecated: use `database`.", + "in": "query", + "name": "tenant_id", + "schema": { + "deprecated": true, + "example": "tenant_1234", + "type": "string", + "x-deprecated": "true" + } + }, + { + "deprecated": true, + "description": "Deprecated: use `collection`.", + "in": "query", + "name": "sub_tenant_id", + "schema": { + "deprecated": true, + "example": "sub_tenant_4567", + "type": "string", + "x-deprecated": "true" + } + }, + { + "deprecated": true, + "description": "Deprecated: omit it. Kept for older databases. Selects the corpus to read: `knowledge` (default) or `memory`.", + "in": "query", + "name": "type", + "schema": { + "deprecated": true, + "enum": [ + "knowledge", + "memory", + "all" + ], + "type": "string", + "x-deprecated": "true" + } + }, + { + "description": "Maximum number of hops to traverse from the start context, from `1` to `10`. Default `5`.", + "in": "query", + "name": "depth", + "schema": { + "default": 5, + "maximum": 10, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Maximum number of member contexts to return, from `1` to `1000`. Default `200`. `is_truncated` is `true` when this cut the traversal short.", + "in": "query", + "name": "max_sources", + "schema": { + "default": 200, + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Principals to answer as. The subgraph contains only contexts they may see, checked at every hop. Repeat the parameter (`acl=a\u0026acl=b`) or send a comma-separated list. Omit it for no access-control scoping.", + "in": "query", + "name": "acl", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + }, + "style": "form" } ], "responses": { @@ -12154,7 +7691,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantDeleteResponse" + "$ref": "#/components/schemas/handler.Envelope-graph_SourceSubgraphResponse" } } }, @@ -12169,26 +7706,6 @@ } }, "description": "Bad Request" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Internal Server Error" } }, "security": [ @@ -12196,33 +7713,46 @@ "BearerAuth": [] } ], - "summary": "Delete a collection", + "summary": "Get connected subgraph", "tags": [ - "database-management" + "context" ], - "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "deleteCollection" - }, - "get": { - "description": "List all collections for a given database", + "x-fern-sdk-group-name": "context", + "x-fern-sdk-method-name": "subgraph" + } + }, + "/context/{id}/metadata": { + "patch": { + "description": "Merge attribute and custom attribute values, or replace the access-control list, of one existing context without re-ingesting it. Keys in the request are inserted or overwritten; keys not sent are kept. `database` and `collection` are required.", "parameters": [ { - "description": "Database identifier", - "in": "query", - "name": "database", + "description": "`context_id` of the context to update.", + "in": "path", + "name": "id", "required": true, "schema": { - "example": "acme_corp", + "example": "HydraDoc1234", "type": "string" } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.contextMetadataUpdateRequest" + } + } + }, + "description": "Metadata update request", + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantIdsResponse" + "$ref": "#/components/schemas/handler.Envelope-github_com_hydradb_hydradb-application_internal_service_MetadataEditResult" } } }, @@ -12264,20 +7794,19 @@ "BearerAuth": [] } ], - "summary": "List collections", + "summary": "Update source metadata", "tags": [ - "database-management" + "context" ], - "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "collections" + "x-fern-sdk-group-name": "context" } }, - "/databases/stats": { - "get": { - "description": "Get collection statistics for a database", + "/databases": { + "delete": { + "description": "Delete a database and all associated data", "parameters": [ { - "description": "Database identifier", + "description": "Database identifier to delete", "in": "query", "name": "database", "required": true, @@ -12292,7 +7821,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantStatsResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantDeleteResponse" } } }, @@ -12334,35 +7863,68 @@ "BearerAuth": [] } ], - "summary": "Get database statistics", + "summary": "Delete a database", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "stats" - } - }, - "/databases/status": { + "x-fern-sdk-method-name": "delete" + }, "get": { - "description": "Check the infrastructure provisioning status for a database", - "parameters": [ + "description": "List all databases for the authenticated user", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantIdsResponse" + } + } + }, + "description": "OK" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "security": [ { - "description": "Database identifier", - "in": "query", - "name": "database", - "required": true, - "schema": { - "example": "acme_corp", - "type": "string" + "BearerAuth": [] + } + ], + "summary": "List databases", + "tags": [ + "database-management" + ], + "x-fern-sdk-group-name": "databases", + "x-fern-sdk-method-name": "list" + }, + "post": { + "description": "Create a new database with optional custom metadata schema", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tenants.TenantCreateRequest" + } } - } - ], + }, + "description": "Database creation request", + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_InfraStatusResponseV2" + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantCreateAcceptedResponse" } } }, @@ -12378,7 +7940,7 @@ }, "description": "Bad Request" }, - "404": { + "403": { "content": { "application/json": { "schema": { @@ -12386,7 +7948,17 @@ } } }, - "description": "Not Found" + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Conflict" }, "500": { "content": { @@ -12404,46 +7976,45 @@ "BearerAuth": [] } ], - "summary": "Get infrastructure status", + "summary": "Create a database", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "status" + "x-fern-sdk-method-name": "create" } }, - "/databases/{database}": { - "patch": { - "description": "Rename a database in place. The internal identity (and therefore all indexed data, graphs and documents) is unchanged — only the caller-facing name moves, atomically. Connectors syncing into the database are repointed at the new name. The old name stops resolving immediately, so callers must switch to the new name in the same rollout.", + "/databases/collections": { + "delete": { + "description": "Permanently remove one collection and all of its data from a database. The database itself is left intact.", "parameters": [ { - "description": "Current database identifier", - "in": "path", + "description": "Database identifier", + "in": "query", "name": "database", "required": true, "schema": { "example": "acme_corp", "type": "string" } + }, + { + "description": "Collection identifier", + "in": "query", + "name": "collection", + "required": true, + "schema": { + "example": "team_docs", + "type": "string" + } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tenants.TenantRenameRequest" - } - } - }, - "description": "New database name", - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantRenameResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantDeleteResponse" } } }, @@ -12469,16 +8040,6 @@ }, "description": "Not Found" }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Conflict" - }, "500": { "content": { "application/json": { @@ -12495,21 +8056,19 @@ "BearerAuth": [] } ], - "summary": "Rename a database", + "summary": "Delete a collection", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "rename" - } - }, - "/databases/{database}/instructions": { + "x-fern-sdk-method-name": "deleteCollection" + }, "get": { - "description": "Read the custom ingestion instructions configured for a database and for its collections.", + "description": "List all collections for a given database", "parameters": [ { "description": "Database identifier", - "in": "path", + "in": "query", "name": "database", "required": true, "schema": { @@ -12523,7 +8082,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.instructionsResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantIdsResponse" } } }, @@ -12565,19 +8124,21 @@ "BearerAuth": [] } ], - "summary": "Get ingestion instructions", + "summary": "List collections", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "get_instructions" - }, - "patch": { - "description": "Set or clear the custom ingestion instructions for a database and its collections. Database instructions apply to every document; a collection's instructions apply on top of them. Both stack with any connector- or resource-level instructions rather than replacing them. Applies from the next ingestion; already-indexed data is not reprocessed.", + "x-fern-sdk-method-name": "collections" + } + }, + "/databases/stats": { + "get": { + "description": "Get collection statistics for a database", "parameters": [ { "description": "Database identifier", - "in": "path", + "in": "query", "name": "database", "required": true, "schema": { @@ -12586,23 +8147,12 @@ } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.instructionsUpdateReq" - } - } - }, - "description": "Instructions to set or clear", - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.instructionsResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_TenantStatsResponse" } } }, @@ -12644,21 +8194,21 @@ "BearerAuth": [] } ], - "summary": "Update ingestion instructions", + "summary": "Get database statistics", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "update_instructions" + "x-fern-sdk-method-name": "stats" } }, - "/databases/{database}/metadata-schema": { + "/databases/status": { "get": { - "description": "Read the database's declared metadata schema fields. Returns the same field shape accepted by database creation and by Update Metadata Schema, so the response round-trips into add_fields.", + "description": "Check the infrastructure provisioning status for a database", "parameters": [ { "description": "Database identifier", - "in": "path", + "in": "query", "name": "database", "required": true, "schema": { @@ -12672,7 +8222,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_TenantMetadataSchemaResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_InfraStatusResponseV2" } } }, @@ -12714,15 +8264,17 @@ "BearerAuth": [] } ], - "summary": "Get metadata schema", + "summary": "Get infrastructure status", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "get_metadata_schema" - }, + "x-fern-sdk-method-name": "status" + } + }, + "/databases/{database}/metadata-schema": { "patch": { - "description": "Add new metadata schema fields to an existing database. Additive only — existing fields cannot be deleted or retyped.", + "description": "Add new fields to an existing database's attributes schema. Additive only: existing fields cannot be deleted, renamed or retyped.", "parameters": [ { "description": "Database identifier", @@ -12822,7 +8374,7 @@ } } }, - "description": "Feedback submission", + "description": "Send `feedback`, `ground_truth`, or both. A request with neither is rejected with `400`.", "required": true }, "responses": { @@ -12882,7 +8434,7 @@ }, "/query": { "post": { - "description": "Unified query endpoint that dispatches across type and query_by (hybrid/text). Optionally filter by one or more exact document titles with `titles`; these are resolved to source IDs before normal retrieval. Filter with `attributes` (an operator language, pushed into the vector search); `metadata_filters` is deprecated in favour of it and still works. `type` is knowledge (the default), memory, or all (both, merged). Prefer sub_tenant_ids for sub-tenant scoping; legacy sub_tenant_id is deprecated for /query and cannot be sent together with sub_tenant_ids.", + "description": "Search a database and return ranked chunks, graph paths, forceful relations and `llm_prompt`, a prompt-ready rendering of all of them. Scope with `collection` or `collections`, filter with `attributes`, or restrict the search to known contexts with `ids` or `titles`.", "requestBody": { "content": { "application/json": { @@ -12891,7 +8443,7 @@ } } }, - "description": "Unified query request", + "description": "The query and how to scope and rank it.", "required": true }, "responses": { @@ -13213,100 +8765,6 @@ "x-fern-sdk-method-name": "retry_delivery" } }, - "/webhooks/indexing/signing-secret": { - "delete": { - "description": "Removes the stored signing secret. Deliveries stop carrying the X-HydraDB-Signature header. This is the only way to disable signing - editing a registration never clears the secret.", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.Envelope-webhooks_WebhookRegisterResponse" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Disable webhook signing", - "tags": [ - "webhooks" - ], - "x-fern-sdk-group-name": "webhooks", - "x-fern-sdk-method-name": "clearSigningSecret" - }, - "post": { - "description": "Generates a signing secret, or stores one you supply. The plaintext is returned exactly once and cannot be retrieved afterwards. Takes effect immediately, so rotate only once your receiver accepts the new secret.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/webhooks.SigningSecretRequest" - } - } - }, - "description": "Omit the body to have a secret generated for you" - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.Envelope-webhooks_SigningSecretResponse" - } - } - }, - "description": "OK" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Unprocessable Entity" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Generate or set the webhook signing secret", - "tags": [ - "webhooks" - ], - "x-fern-sdk-group-name": "webhooks", - "x-fern-sdk-method-name": "setSigningSecret" - } - }, "/webhooks/indexing/test": { "post": { "description": "Send a test webhook event to the registered endpoint to verify connectivity.", @@ -13344,80 +8802,6 @@ "x-fern-sdk-group-name": "webhooks", "x-fern-sdk-method-name": "test" } - }, - "/webhooks/supabase": { - "post": { - "description": "Ingest a Supabase INSERT/UPDATE/DELETE row change into the graph.", - "parameters": [ - { - "description": "Connector id", - "in": "header", - "name": "X-HydraDB-Connector", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object" - } - }, - "text/plain": { - "schema": { - "title": "request", - "type": "object" - } - } - }, - "description": "Supabase Database Webhook payload", - "required": true - }, - "responses": { - "202": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.Envelope-handler_supabaseWebhookAck" - } - } - }, - "description": "Accepted" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Not Found" - } - }, - "security": [ - { - "BearerAuth": [] - } - ], - "summary": "Supabase row-change webhook", - "tags": [ - "webhooks" - ] - } } }, "servers": [ diff --git a/api-reference/v2/sdks.mdx b/api-reference/v2/sdks.mdx index 175fddaa..2ef82521 100644 --- a/api-reference/v2/sdks.mdx +++ b/api-reference/v2/sdks.mdx @@ -409,7 +409,7 @@ row = client.context.list( ) # Read the stored content behind a context_id. -item = client.context.inspect( +inspected = client.context.inspect( database="my_first_database", collection="support", id="refund-policy", @@ -446,7 +446,7 @@ const row = await client.context.list({ }); // Read the stored content behind a context_id. -const item = await client.context.inspect({ +const inspected = await client.context.inspect({ database: "my_first_database", collection: "support", id: "refund-policy", diff --git a/essentials/v2/query.mdx b/essentials/v2/query.mdx index a7444a95..f9c14e46 100644 --- a/essentials/v2/query.mdx +++ b/essentials/v2/query.mdx @@ -218,7 +218,6 @@ See [When to use each](/essentials/v2/databases-and-collections#2-when-to-use-ea | --- | --- | --- | | `temporal_reasoning` | boolean | Default `true`. Resolve time-based questions (current, as of, ranges, upcoming). Matched facts come back in `chunks[].temporal`. Never changes which chunks are returned. | | `temporal_now` | ISO 8601 string | The time to treat as now. Set it when replaying past conversations. | -| `temporal_intent` | object | Override the temporal intent HydraDB would infer from the query. | --- From 9c8047885a97faea98f7bc537619eb1e18bf86c4 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 09:12:30 +0530 Subject: [PATCH 11/17] docs: API reference flags every legacy-only field deprecated api-reference/v2/openapi.json is hydradb-application's docs view at app PR #1671. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/v2/openapi.json | 41 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index 77e0c037..8dbf7855 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -284,7 +284,7 @@ }, "sub_tenant_id": { "deprecated": true, - "description": "deprecated: use collection", + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "minLength": 1, "type": "string", @@ -293,7 +293,7 @@ }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "minLength": 1, "type": "string", @@ -2741,7 +2741,8 @@ "type": "integer" }, "status": { - "description": "Always `active`; kept for compatibility. Read `lifecycle` for what the connector is doing.", + "deprecated": true, + "description": "Deprecated: always `active`. Read `lifecycle` for what the connector is doing.", "example": "completed", "type": "string" }, @@ -2970,7 +2971,8 @@ "type": "integer" }, "status": { - "description": "Always `active`; kept for compatibility. Read `lifecycle` for what the connector is doing.", + "deprecated": true, + "description": "Deprecated: always `active`. Read `lifecycle` for what the connector is doing.", "example": "completed", "type": "string" }, @@ -3493,7 +3495,7 @@ }, "sub_tenant_id_override": { "deprecated": true, - "description": "deprecated: use collection_override", + "description": "Deprecated: use `collection_override`.", "type": "string", "x-deprecated": "true" }, @@ -3862,7 +3864,8 @@ "type": "string" }, "filename": { - "description": "Name of the uploaded file. Only returned for the deprecated `documents` upload.", + "deprecated": true, + "description": "Deprecated: returned only for the deprecated `documents` upload. Name of the uploaded file.", "example": "policy.pdf", "type": "string" }, @@ -3877,12 +3880,14 @@ "type": "boolean" }, "relations_created": { - "description": "Number of forceful relations created for this entry. Only returned by the deprecated `documents` and `app_knowledge` fields, for entries that declared relations.", + "deprecated": true, + "description": "Deprecated: returned only for the deprecated `documents` and `app_knowledge` fields. Number of forceful relations created for this entry.", "example": 5, "type": "integer" }, "relations_error": { - "description": "Why this entry's forceful relations could not be created. The entry is still queued. Only returned by the deprecated `documents` and `app_knowledge` fields.", + "deprecated": true, + "description": "Deprecated: returned only for the deprecated `documents` and `app_knowledge` fields. Why this entry's forceful relations could not be created; the entry is still queued.", "type": "string" }, "status": { @@ -4134,7 +4139,8 @@ "type": "integer" }, "user_memories": { - "description": "Rows when the deprecated `type: \"memory\"` is sent. Same fields as `sources`, keyed by `memory_id`.", + "deprecated": true, + "description": "Deprecated: returned only when the deprecated `type: \"memory\"` is sent. Read `sources`.", "example": [ { "additional_metadata": { @@ -5007,7 +5013,7 @@ }, "query_forceful_relations": { "deprecated": true, - "description": "Deprecated alias for follow_forceful_relations. Ignored when follow_forceful_relations is sent.", + "description": "Deprecated: use `follow_forceful_relations`. Ignored when that is sent.", "example": true, "type": "boolean", "x-deprecated": "true" @@ -5075,7 +5081,7 @@ }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -5213,7 +5219,8 @@ "x-deprecated": "true" }, "user_memory_deleted": { - "description": "Number of deletions from the memory corpus. Returned only on older databases, and only when `type` selects `memory` or `all`.", + "deprecated": true, + "description": "Deprecated: returned only when the deprecated `type` selects `memory` or `all`. Read `deleted_count`.", "example": 1, "type": "integer" } @@ -5544,7 +5551,7 @@ }, "sub_tenant_ids": { "deprecated": true, - "description": "Deprecated alias for `collections`.", + "description": "Deprecated: use `collections`. Same value.", "example": [ "sub_tenant_4567", "sub_tenant_8901" @@ -5623,14 +5630,14 @@ }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" }, "tenant_metadata_schema": { "deprecated": true, - "description": "deprecated: use database_metadata_schema", + "description": "Deprecated: use `database_metadata_schema`.", "items": { "$ref": "#/components/schemas/tenants.CustomPropertyDefinition" }, @@ -5722,7 +5729,7 @@ }, "failed_tenant_ids": { "deprecated": true, - "description": "Deprecated alias for `failed_databases`.", + "description": "Deprecated: use `failed_databases`. Same value.", "example": [ { "database": "acme_corp", @@ -5744,7 +5751,7 @@ }, "tenant_ids": { "deprecated": true, - "description": "Deprecated alias for `databases`.", + "description": "Deprecated: use `databases`. Same value.", "example": [ "tenant_1234", "tenant_5678" From 66ab00b92d7a0bc0ce74b7a58f3988cdf8356a02 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 09:55:48 +0530 Subject: [PATCH 12/17] docs: one- or two-line summaries in every reference table Table cells ran to 900 characters of rationale and sub-field inventories. Every cell in the pages this stack touches is now at most 160 characters: what the field is and the one fact a caller needs. The spec is the docs view at app PR #1672, which holds field descriptions to 200 characters. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 22 +-- .../v2/endpoint/configure-connector.mdx | 4 +- .../v2/endpoint/create-connector.mdx | 4 +- .../v2/endpoint/delete-collection.mdx | 4 +- api-reference/v2/endpoint/fetch-content.mdx | 2 +- api-reference/v2/endpoint/ingest-context.mdx | 14 +- api-reference/v2/endpoint/list-documents.mdx | 4 +- api-reference/v2/endpoint/query-overview.mdx | 26 +-- api-reference/v2/endpoint/query.mdx | 8 +- api-reference/v2/endpoint/source-status.mdx | 8 +- api-reference/v2/endpoint/submit-feedback.mdx | 2 +- .../v2/endpoint/update-metadata-schema.mdx | 6 +- .../v2/endpoint/update-source-metadata.mdx | 7 +- api-reference/v2/error-responses.mdx | 4 +- api-reference/v2/index.mdx | 4 +- api-reference/v2/openapi.json | 174 +++++++++--------- essentials/v2/access-control.mdx | 6 +- essentials/v2/api-results.mdx | 26 +-- essentials/v2/attributes.mdx | 12 +- essentials/v2/connectors.mdx | 2 +- essentials/v2/graph-collections-byog.mdx | 4 +- essentials/v2/ingest.mdx | 8 +- essentials/v2/query.mdx | 54 +++--- essentials/v2/webhooks.mdx | 8 +- plugins/claude-code.mdx | 2 +- plugins/mcp.mdx | 12 +- 26 files changed, 215 insertions(+), 212 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index 7db92ac9..9281b6e9 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -667,18 +667,18 @@ Each context carries exactly one of `text` or `conversation`. | Field | Notes | |---|---| -| `context_id` | Your id for the context. When omitted it is generated from its text and `title`, so two contexts without ids that have the same text and the same (or no) title collide. Must not contain commas. | +| `context_id` | Your id for the context; no commas. Omitted: derived from text and `title`, so identical untitled contexts collide. | | `title` | Optional readable name, printed in `llm_prompt` and matchable with `titles` on `/query`. At most 1,024 bytes. | | `text` | Plain text or markdown. | | `conversation` | A list of `{ role, content }` turns. | -| `enrich` | Extract entities, relations and preferences from this context. Default: the request's `enrich`, else `true`. Set `false` to store the context only as searchable text. | +| `enrich` | Extract entities, relations and preferences. Default: the request's `enrich`, else `true`; `false` stores searchable text only. | | `upsert` | Replace an existing context with the same `context_id`. Default: the request's `upsert`, else `true`. | | `instructions` | Steer enrichment for this context. At most 4,000 characters. Default: the request's `instructions`. | -| `happened_at` | The date the context is about, `YYYY-MM-DD` only; a timestamp is a `400`. HydraDB records when it received the context separately and returns that as `received_at` on query chunks. | +| `happened_at` | The date the context is about, `YYYY-MM-DD` only (a timestamp is a `400`). Distinct from `received_at`. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. | | `custom_attributes` | Free-form fields. Not filterable. | -| `forceful_relations` | `{ "context_ids": [...], "properties": {} }`: the `context_id`s this context is linked to. `properties` is an optional flat map of string, number or boolean values (at most 1 KiB) stored on each edge; `id`, `created_at`, `relation_type`, `tenant_id` and `sub_tenant_id` are reserved keys. | -| `acl` | Principals allowed to retrieve the context: `user_email:a@x.com` (or a bare email), `group::`, `domain:acme.com`, `__public__`. Omit for unrestricted, `[]` for nobody. A malformed principal rejects the whole request with `400`. | +| `forceful_relations` | `{ "context_ids": [...], "properties": {} }`: linked `context_id`s, plus optional flat edge properties (max 1 KiB, some keys reserved). | +| `acl` | Principals who may retrieve it: email, `group::`, `domain:`, `__public__`. Omit for unrestricted, `[]` for nobody; malformed is `400`. | | `user_name` | The speaker for the context: the author of a text context, or the person in a conversation's `user` turns. Default `"User"`. | An unknown key is a `400` naming the key and listing the accepted ones, whether it is on the request, on a context, on a conversation turn or inside `forceful_relations`. @@ -1006,8 +1006,8 @@ Attribute-filtered search, on behalf of one user: | `content` | The chunk's own text, verbatim. Enrichment is never concatenated into it. | | `enrichment` | A plain string: what enrichment extracted from this chunk (a preference, a fact). Omitted when there is none. | | `enrichment_kind` | An optional label; omitted when none was set. | -| `received_at` | When HydraDB received the context, as an RFC 3339 timestamp. This is the ingest time, not its `happened_at` (which is not returned). Omitted when no receipt time is recorded, as on context ingested before it existed; never sent empty. | -| `temporal` | Present only when the query engaged temporal reasoning: `{ content, start_date, end_date }` entries, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`. | +| `received_at` | Ingest time as RFC 3339, not `happened_at`. Omitted when no receipt time is recorded. | +| `temporal` | `{ content, start_date, end_date }` entries; present only when temporal reasoning ran. Either date may be `null`. | Chunks carry almost nothing about their source: no title, url, collection or attributes, only `received_at`. `llm_prompt` prints the title, collection, last-updated date and url for the model. To read a context's stored content yourself, call `GET /context/inspect` with the chunk's `context_id`; to read its title and attributes, call `POST /context/list` with `ids: [context_id]`. @@ -1049,10 +1049,10 @@ Sections, in order (a section with nothing in it is left out; when the query ret | Section | Contents | |---|---| -| `# Query results` | The query, an `**Interpreted:**` line when an alias or resolved reference widened it, a `**Found:**` line counting what follows, a `**Note:**` line when a lookup degraded, and (when there is a result) the line telling the model to cite it by its number. | -| `## Results` | One `### 1. title` block per chunk, in ranked order: relevance, collection, type, category (`enrichment_kind`, when set), id and last-updated date, the chunk's `content`, then `**Enrichment:**`. | +| `# Query results` | The query, then optional `**Interpreted:**`, `**Found:**` and `**Note:**` lines and a cite-by-number instruction. | +| `## Results` | One `### 1. title` block per chunk in rank order: metadata line, `content`, then `**Enrichment:**`. | | `## Forceful relations` | One `### R1. title` block per forceful-relation chunk, with `**Linked from:**` naming the context that pulled it in. | -| `## Related facts` | One line per graph path, such as `- [P1] **Refunds** -managed_by→ **Finance** (relevance 0.81) [1]`, with the `path_summary` indented under it unless it only restates the chain. A path without a reranked score has no parenthetical. | +| `## Related facts` | One `[P1]` line per graph path, with its relevance when reranked and its `path_summary` indented below. | | `## Temporal facts` | A `**Duration:**` line first for a "how long between" question, then one line per dated fact the query engaged, with its resolved window, citing its result. | | `## Source facts` | App-native facts about the sources behind the results (who, role, where, thread, connector, synced). Prompt only. | | `## Profiles` | The entity profiles the query selected. Prompt only. | @@ -1216,7 +1216,7 @@ Declared at `POST /databases` (or added later with `PATCH /databases/{database}/ | Field | Purpose | |---|---| | `name` | Attribute key. Starts with a letter; letters, numbers and underscores only; not a reserved system name such as `chunk_id`. | -| `data_type` | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the aliases `string`, `boolean`, `integer`, `float`, `object`. Default `VARCHAR`. Arrays are not supported. | +| `data_type` | `VARCHAR` (default), `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or `string`, `boolean`, `integer`, `float`, `object`. No arrays. | | `enable_match` | Fast exact-match path for a field you filter on often. | | `enable_dense_embedding` | Semantic search over a `VARCHAR` field. | | `enable_sparse_embedding` | BM25 search over a `VARCHAR` field. | diff --git a/api-reference/v2/endpoint/configure-connector.mdx b/api-reference/v2/endpoint/configure-connector.mdx index 50b7b3aa..82bfe6da 100644 --- a/api-reference/v2/endpoint/configure-connector.mdx +++ b/api-reference/v2/endpoint/configure-connector.mdx @@ -50,7 +50,7 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/configure' \ | Name | Description | | --- | --- | | | Resources to activate. Each entry corresponds to one entry from [Discover](/api-reference/v2/endpoint/discover-connector-resources). | -| | How far back the first sync fetches historical data. Only applies to the initial sync; later syncs are incremental from the last cursor. Above `30`, some providers fetch the older history in background chunks, and the response then reports `backfill: true`. (default: `30`) | +| | History the first sync fetches; later syncs are incremental. Above `30`, some providers backfill in the background (`backfill: true`). (default: `30`) | ### Resource entry fields @@ -60,7 +60,7 @@ curl -X POST 'https://api.hydradb.com/connectors/{connector_id}/configure' \ | | Resource type from `GET /connectors/:id/discover` (e.g. `channel`, `repo`, `linear_team`). | | | Display name for this resource. | | | Routes synced objects from this resource into a specific collection. Overrides the connector-level `collection`. (deprecated alias: `sub_tenant_id`) | -| | Key-value pairs merged into the attributes of every synced object from this resource. Undeclared keys are accepted and stored, but only keys declared in `database_metadata_schema` are indexed for filtering. | +| | Key-value pairs merged into the attributes of every synced object from this resource. Only keys in `database_metadata_schema` are filterable. | | | Key-value pairs merged into the custom attributes of every synced object from this resource. Free-form, no schema required. | See [Connectors: Overview](/api-reference/v2/endpoint/connectors-overview) for how these merge with system-generated fields. diff --git a/api-reference/v2/endpoint/create-connector.mdx b/api-reference/v2/endpoint/create-connector.mdx index 6860370e..c77c2f0f 100644 --- a/api-reference/v2/endpoint/create-connector.mdx +++ b/api-reference/v2/endpoint/create-connector.mdx @@ -37,8 +37,8 @@ curl -X POST 'https://api.hydradb.com/connectors' \ | | Human-readable label for this connector. | | | Which database receives the synced data. (deprecated alias: `tenant_id`) | | | Default collection partition for synced objects. Individual resources can override this. (deprecated alias: `sub_tenant_id`; default: `""`) | -| | Identifier for the external account (e.g. Slack workspace ID, GitHub org name). It is part of every synced context's ID, so use a distinct value for each connector of the same provider. | -| | Provider-specific credentials, matching the provider's `credential_schema` from [Get Connector Provider](/api-reference/v2/endpoint/get-connector-provider). Token-based providers typically take `{ "api_token": "..." }` or `{ "access_token": "..." }`. | +| | External account id, such as a Slack workspace ID. Part of every synced context's ID, so keep it distinct per connector. | +| | Credentials matching the provider's `credential_schema` from [Get Connector Provider](/api-reference/v2/endpoint/get-connector-provider). | | | Seconds between scheduled syncs. From `300` to `604800`; a few providers set a higher minimum or a lower maximum. (default: `3600`) | | | Instructions that steer how this connector's synced documents are ingested and indexed. Up to 4000 characters. | diff --git a/api-reference/v2/endpoint/delete-collection.mdx b/api-reference/v2/endpoint/delete-collection.mdx index 08524aa9..3e9454b5 100644 --- a/api-reference/v2/endpoint/delete-collection.mdx +++ b/api-reference/v2/endpoint/delete-collection.mdx @@ -31,8 +31,8 @@ curl -X DELETE 'https://api.hydradb.com/databases/collections?database=my_first_ ## Query parameters | Name | Description | | --- | --- | -| | Identifier of the database that owns the collection. Formerly `tenant_id`; the API still accepts the `tenant_id` alias in its place (deprecated), though the SDKs and OpenAPI spec model only the canonical name. | -| | Identifier of the collection to delete. Formerly `sub_tenant_id`; the API still accepts the `sub_tenant_id` alias in its place (deprecated). Unlike the read endpoints this has no default: a delete has no safe default collection. | +| | Database that owns the collection. Alias `tenant_id` (deprecated). | +| | Collection to delete. No default. Alias `sub_tenant_id` (deprecated). | diff --git a/api-reference/v2/endpoint/fetch-content.mdx b/api-reference/v2/endpoint/fetch-content.mdx index 45b37c05..edd27199 100644 --- a/api-reference/v2/endpoint/fetch-content.mdx +++ b/api-reference/v2/endpoint/fetch-content.mdx @@ -47,7 +47,7 @@ curl -G 'https://api.hydradb.com/context/inspect' \ | --- | --- | | | ID of the context to fetch. | | | Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | +| | Collection scope; the default collection when omitted. Alias `sub_tenant_id` (deprecated). (default=`null`) | | | What to return. See [Fetch modes](#fetch-modes). (default=`"both"`) | | | TTL of the presigned URL (when `mode` includes `url`). Range `60 ≤ x ≤ 604800` (7 days). (default=`3600`) | diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index 2ee9101a..45ad427b 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -124,18 +124,20 @@ Each entry in `context` is exactly one of `text` or `conversation`. | Name | Description | | --- | --- | -| | Your id for the context; the upsert key. Generated when omitted. At most 100 bytes. Must not contain a comma (`,`), which is the id separator on `/context/status?ids=`, and must not start with `att_` or `cmt_` (reserved for connector ids). | +| | Your id and the upsert key; generated when omitted. At most 100 bytes, no commas, and no `att_` or `cmt_` prefix. | | | Readable name. Searchable with `titles` on `/query`. Trimmed, then at most 1,024 bytes of UTF-8. | | | Plain text. Send exactly one of `text` or `conversation`. | -| | Turns of `{ role, content }`; roles are `user`, `assistant` and `system`. A turn takes no other key; the speaker is the context's `user_name`. `system` turns are never stored as facts: when neither the context nor the request sets `instructions`, they become its instructions and count toward the same 4,000-character limit; otherwise they are dropped. A conversation needs at least one `user` or `assistant` turn, and no turn may have empty `content`. | -| | Extract entities, relations and preferences from this context into the graph; the output is stored separately and returned as `enrichment` on query. (default=the request's `enrich`, else `true`) | +| | Turns of `{ role, content }`, role `user`, `assistant` or `system`. Needs one `user` or `assistant` turn; no empty `content`. | +| | `system` turns are never stored as facts. They become the context's `instructions` when none are set, and are dropped otherwise. | +| | Extract entities, relations and preferences into the graph, returned as `enrichment` on query. (default=the request's `enrich`, else `true`) | | | Replace an existing context with the same `context_id`, deleting its chunks and graph contribution first. (default=the request's `upsert`, else `true`) | | | Steer enrichment for this context. At most 4,000 characters after trimming. (default=the request's `instructions`) | -| | The date the context is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the context separately and returns that as `received_at` on query chunks. | +| | The date the context is about, `YYYY-MM-DD` only; a timestamp is a `400`. Receipt time is returned separately as `received_at`. | | | Declared, filterable fields; keys must be in `database_metadata_schema`. Filter with `attributes` on `/query`. See [Attributes](/essentials/v2/attributes). | | | Free-form fields. Stored with the context; not filterable and not returned on query chunks. | -| | Relations you declare to other contexts: `{ "context_ids": ["", ...], "properties": {} }`. Followed on `/query` in `thinking` mode with `follow_forceful_relations` and returned in `forceful_relations[]`. Each id follows the same rules as `context_id`. `properties` is optional and is stored on every edge the context declares: a flat map of string, number or boolean values, at most 1 KiB as compact JSON, with no empty key and none of the reserved keys `id`, `created_at`, `relation_type`, `tenant_id` or `sub_tenant_id`. | -| | Principals allowed to retrieve the context: bare emails or `user_email:`, `group:`, `domain:` principals, or `__public__`. Omit for unrestricted; `[]` for nobody. A malformed principal rejects the whole request with `400`. See [Access control](/essentials/v2/access-control). | +| | `{ "context_ids": [...], "properties": {} }` links to other contexts, followed in `thinking` mode via `follow_forceful_relations`. Ids use `context_id` rules. | +| | Optional flat map of string, number or boolean values stored on each edge. At most 1 KiB; no empty key and no reserved key such as `id`. | +| | Allowed principals: emails, `user_email:`, `group:`, `domain:`, or `__public__`. Omit for unrestricted, `[]` for nobody; malformed is a `400`. | | | The speaker for the context: the author of a `text` context, or the person in a conversation's `user` turns. (default=`"User"`) | ### Limits diff --git a/api-reference/v2/endpoint/list-documents.mdx b/api-reference/v2/endpoint/list-documents.mdx index ba531c04..65b0ffdf 100644 --- a/api-reference/v2/endpoint/list-documents.mdx +++ b/api-reference/v2/endpoint/list-documents.mdx @@ -82,9 +82,9 @@ curl -X POST 'https://api.hydradb.com/context/list' \ | Category | Matched against | Notes | | --- | --- | --- | -| | The context's schema-aligned `metadata` payload | Use for database metadata fields. `tenant_metadata` is accepted as a legacy alias. Each key is matched against the stored value; no `enable_match` declaration is needed on this endpoint. | +| | The context's schema-aligned `metadata` payload | Exact match per key on database metadata fields; no `enable_match` needed. Alias `tenant_metadata`. | | | The context's `additional_metadata` payload | Free-form JSON per context. No schema declaration required. `document_metadata` is accepted as a legacy alias. | -| | Built-in fields: `type`, `title`, `description`, `url`, `timestamp`, and the connector fields `app_provider`, `app_kind`, `app_external_id`, `app_parent_id` | Use for connector categories or quick title lookups. Any other key returns `400`. `app_external_id` and `app_parent_id` are only unique per provider, so pair them with `app_provider`. | +| | Built-in fields: `type`, `title`, `description`, `url`, `timestamp`, `app_provider`, `app_kind`, `app_external_id`, `app_parent_id` | Other keys are a `400`. Pair `app_external_id` and `app_parent_id` with `app_provider`. | ### 2. Including Fields for convenient data objects diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index aac260ea..b4486255 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -34,15 +34,15 @@ linkStyle default stroke:#64748b,stroke-width:2px; | Parameter | Values | Use it for | |---|---|---| -| | `string[]` or weighted object | Where to look. A list uses equal normalized weights; an object like `{ "user_alex": 2, "company": 1 }` ranks one scope above another without excluding either. Max 100 collections. `collection` selects a single one. | -| | `"hybrid"`, `"text"` | Choose the matching method. Use `"hybrid"` by default and `"text"` for exact terms or phrases. | -| | `"fast"`, `"thinking"`, `"auto"` | Choose latency vs quality, or let HydraDB decide. `"fast"` for low-latency paths, `"thinking"` for multi-query retrieval, reranking and declared relations, `"auto"` to score the query and route to one of the two (defaults to `"thinking"` when the signal is inconclusive; **the default if `mode` is omitted**). | -| | integer | Control prompt size. Start with `10`, reduce for tight context windows, increase only when you rerank or summarize downstream. | -| | `0.0` to `1.0` or `"auto"` | Tune hybrid query. Lower values favor BM25 keywords; higher values favor semantic similarity. Defaults to `0.8`; `"auto"` also resolves to `0.8`. | -| | object | Narrow candidates with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on the fields declared in `database_metadata_schema`. | -| | boolean | Include graph paths in `graph[]`. On by default; set `false` for chunk-only responses. | -| | boolean | Pull context linked with `forceful_relations` at ingest into `forceful_relations[]`. On by default; followed only in `thinking` mode. | -| | boolean | Adds app-aware retrieval for connector content while still querying the full selected scope. On by default; set `false` to skip it. | +| | `string[]` or weighted object | Where to look. A list weights collections equally; an object like `{ "user_alex": 2, "company": 1 }` sets relative weights. Max 100. | +| | `"hybrid"`, `"text"` | `"hybrid"` by default; `"text"` for exact terms or phrases. | +| | `"fast"`, `"thinking"`, `"auto"` | `"fast"` for low latency, `"thinking"` for reranking and declared relations, `"auto"` (default) to route between them. | +| | integer | Control prompt size. Default `10`. | +| | `0.0` to `1.0` or `"auto"` | Lower favors BM25 keywords, higher favors semantic similarity. Default `0.8`. | +| | object | Filter with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on fields declared in `database_metadata_schema`. | +| | boolean | Include graph paths in `graph[]`. Default `true`. | +| | boolean | Add context linked with `forceful_relations` at ingest. Default `true`; `thinking` mode only. | +| | boolean | Add app-aware retrieval for connector content. Default `true`. | For filter design, read [Attributes](/essentials/v2/attributes) before creating database schemas. For exact request fields, defaults, and response shape, use [Query](/api-reference/v2/endpoint/query). @@ -158,10 +158,10 @@ Use text query when literal wording matters: legal clauses, SKUs, error codes, I | Key | Contents | | --- | --- | -| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content`, optional `enrichment` (a string), `enrichment_kind`, `received_at` (when HydraDB received the context) and `temporal`. No other source details; call `POST /context/list` with the `context_id` in `ids` for those. | -| `graph[]` | Paths through the context graph, deduplicated across both origins and not capped: `origin` (`query_path` or `chunk_relation`), `triplets[]` and a `path_summary`, which is never empty. Each hop's `relation.chunk_id` names the chunk it came from, and `relation.timestamp` (Unix epoch seconds) is present when the edge has one; a `chunk_relation` path is only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk. | -| `forceful_relations[]` | Chunks linked with `forceful_relations` at ingest, each with the `via` that brought it in. Followed only in `thinking` mode. | -| `llm_prompt` | A server-built markdown string, ready to inject into a model call: results cited `[1]`, forceful relations `[R1]`, related facts labelled `[P1]` in `graph[]` order with each path's relevance when it has one, then temporal facts and sources. | +| `chunks[]` | Ranked matches with `chunk_id`, `context_id`, `score`, `content`, and optional `enrichment`, `enrichment_kind`, `received_at` and `temporal`. | +| `graph[]` | Paths through the context graph: `origin`, `triplets[]` and `path_summary`. Each hop's `relation.chunk_id` names its chunk. | +| `forceful_relations[]` | Chunks linked at ingest with `forceful_relations`, each with its `via` link. `thinking` mode only. | +| `llm_prompt` | Server-built markdown to inject into a model call. Cites results `[1]`, forceful relations `[R1]` and graph paths `[P1]`. | Inject `llm_prompt` for the model; preserve `chunks[]` order when you render results yourself. See [How to Use API Results](/essentials/v2/api-results). diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index 34d2a542..3ee6956d 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -543,10 +543,10 @@ The generated response schema on this page is a union of two bodies: the older v | Key | Contents | | --- | --- | -| `chunks[]` | Ranked matches: `chunk_id`, `context_id`, `score`, `content` (verbatim), `enrichment` (the extracted statement as a plain string, omitted when there is none), `enrichment_kind` (an optional label; omitted when none was set), `received_at` (when HydraDB received the context, RFC 3339; this is not its `happened_at`, and it is omitted when no receipt time is recorded, as on context ingested before it existed), `temporal[]` (only when the query engaged temporal reasoning; `{ content, start_date, end_date }`, where `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` and either date may be `null`). | -| `graph[]` | Paths through the context graph, query paths first then chunk expansions: `origin`, `triplets[]` of `source` / `relation` / `target`, plus `path_summary`. `origin` is `"query_path"` (grown from the entities in the query) or `"chunk_relation"` (the neighbourhood of a returned chunk, only returned when one of its hops came from a returned chunk or a `forceful_relations` chunk). The array is deduplicated across both origins and is not capped. `path_summary` is never empty: when the server wrote no summary, it narrates the hops. Entities are `{ entity_id, name }`; relations are `{ predicate, context, temporal_details?, timestamp?, relationship_id, chunk_id }`, where `temporal_details` is omitted when empty and `timestamp` (Unix epoch seconds, a float) is omitted when the edge has none. `[]` when `graph_context` is `false`. | -| `forceful_relations[]` | Chunks pulled in through `forceful_relations` declared at ingest, followed only in `thinking` mode: `via.from` (the context whose declaration pulled it in, may be `""`), `via.to` (the chunk's own `context_id`), `chunk` (same shape as `chunks[]`). `[]` when none, when `follow_forceful_relations` is `false`, or when the query ran in `fast` mode. | -| `llm_prompt` | A server-built markdown string ready to inject into a model call: `# Query results`, then `## Results`, `## Forceful relations`, `## Related facts`, `## Temporal facts` (with a `**Duration:**` line for a "how long between" question), `## Source facts`, `## Profiles`, `## Code search` and `## Sources`, each left out when empty. Source facts, profiles, code-search answers and the duration are prompt only: no JSON key carries them. Results are cited `[1]` and forceful relations `[R1]`; related facts are labelled `[P1]`, `[P2]`, ... in `graph[]` order, as in `- [P1] **Refunds** -managed_by→ **Finance** (relevance 0.81) [1]`: the parenthetical is the path's relevance after reranking and is left out when the path has none, and the line ends with the results the path was extracted from. Sources print only web (`http` or `https`) links. `""` only when the query found nothing at all. The layout is on [Query](/essentials/v2/query#llm_prompt). | +| `chunks[]` | Ranked matches with `chunk_id`, `context_id`, `score`, verbatim `content`, and optional `enrichment`, `enrichment_kind`, `received_at` and `temporal[]`. | +| `graph[]` | Paths through the context graph, each with `origin`, `triplets[]` and `path_summary`. `[]` when `graph_context` is `false`. | +| `forceful_relations[]` | Chunks linked at ingest with `forceful_relations`, each with its `via` link. Followed only in `thinking` mode. | +| `llm_prompt` | Server-built markdown to inject into a model call, citing `[1]`, `[R1]` and `[P1]`. See [Query](/essentials/v2/query#llm_prompt). | To show a chunk's graph paths under that chunk, group hops by `triplets[].relation.chunk_id` and match it against `chunks[].chunk_id` (and `forceful_relations[].chunk.chunk_id`). See [Attaching graph paths to chunks](/essentials/v2/query#attaching-graph-paths-to-chunks). diff --git a/api-reference/v2/endpoint/source-status.mdx b/api-reference/v2/endpoint/source-status.mdx index e93dcf2c..2af7b87d 100644 --- a/api-reference/v2/endpoint/source-status.mdx +++ b/api-reference/v2/endpoint/source-status.mdx @@ -47,9 +47,9 @@ curl -G 'https://api.hydradb.com/context/status' \ | Name | Description | | --- | --- | -| | One or more `id` values returned at ingestion. Accepts the ID of any context, including context synced by a connector. Pass either repeated params (`ids=a&ids=b`) or a single comma-joined value (`ids=a,b`); the two forms can be mixed. IDs never contain commas (they are rejected at ingest), so the comma-joined form always splits unambiguously. Surrounding whitespace is trimmed, and empty and duplicate entries are dropped. | +| | Context ids, including connector-synced ones. Repeat the param (`ids=a&ids=b`) or comma-join (`ids=a,b`). | | | Database the context belongs to. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Collection scope. If omitted, the default collection is used. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`null`) | +| | Collection scope; the default collection when omitted. Alias `sub_tenant_id` (deprecated). (default=`null`) | @@ -120,7 +120,7 @@ Each entry in `data.statuses` describes one requested `id`: | `error_code` | string | Machine-readable reason an entry is `errored`; **empty string (`""`) when the entry is not errored.** See [`error_code` values](#error-code-values). | | `error_message` | string | Human-readable explanation of an ingestion-pipeline `error_code`. Empty otherwise, including for `FILE_NOT_FOUND`. | | `success` | boolean | `false` when `indexing_status` is `errored`, otherwise `true`. Describes the context, **not** the HTTP request: a `200` response can contain `errored` entries. | -| `message` | string | Status of the *lookup* itself: "Processing status retrieved successfully", or "ID not found" for an unknown `id`. It does **not** describe the ingestion outcome; read `indexing_status` and `error_code` for that. | +| `message` | string | Result of the lookup, such as "ID not found". For the ingestion outcome, read `indexing_status` and `error_code`. | ### Error code values @@ -129,7 +129,7 @@ Each entry in `data.statuses` describes one requested `id`: | `error_code` | Meaning | What to do | | --- | --- | --- | | `FILE_NOT_FOUND` | No context with this `id` exists in the given `database` and `collection`: usually a typo, an `id` that was never ingested, or a context that was deleted. | Fix the `id`, or ingest the context. Not a processing failure: retrying the status call will not change it. | -| *ingestion-pipeline codes* | A genuine processing failure, reported as a numeric `E####` code (for example `E1001` parse failed, `E1002` unsupported format, `E4001` embedding failed). | Act on the specific code; see [Ingestion error codes](/api-reference/v2/error-responses#ingestion-error-codes). Many are re-ingest-and-retry; some are terminal (unsupported format, empty content). | +| *ingestion-pipeline codes* | A processing failure as an `E####` code, such as `E1001` parse failed. | Act on the code; see [Ingestion error codes](/api-reference/v2/error-responses#ingestion-error-codes). | Branch on `error_code`, not on the text in `message` or `error_message`. `message` describes the lookup, not the ingestion result, and human-readable text may change. The codes an `errored` entry can carry are listed under [Ingestion error codes](/api-reference/v2/error-responses#ingestion-error-codes). diff --git a/api-reference/v2/endpoint/submit-feedback.mdx b/api-reference/v2/endpoint/submit-feedback.mdx index 2f16f25c..418dae04 100644 --- a/api-reference/v2/endpoint/submit-feedback.mdx +++ b/api-reference/v2/endpoint/submit-feedback.mdx @@ -237,7 +237,7 @@ Each submission is stored separately: a second comment about the same query does | Status | When | | --- | --- | -| `400` | `request_id` missing or not a UUID; no usable signal (`feedback` blank or absent **and** `ground_truth` absent, empty, or blank); `feedback`, `ground_truth` or `metadata` over its limits; unknown `rating`/`source`; `collection` without `database` | +| `400` | `request_id` missing or not a UUID; no `feedback` and no `ground_truth`; a field over its limit; unknown `rating`/`source`; `collection` without `database` | | `401` | Missing or invalid API key | | `404` | `database` does not exist or is not reachable by this key | | `429` | Over the rate limit; see `Retry-After` | diff --git a/api-reference/v2/endpoint/update-metadata-schema.mdx b/api-reference/v2/endpoint/update-metadata-schema.mdx index 382815c4..45f74898 100644 --- a/api-reference/v2/endpoint/update-metadata-schema.mdx +++ b/api-reference/v2/endpoint/update-metadata-schema.mdx @@ -91,7 +91,7 @@ Each `add_fields[]` entry uses the same field shape as `database_metadata_schema | Field | Description | | --- | --- | | | New metadata key. Must start with a letter, contain only letters, numbers and underscores, and not be a reserved system name. | -| | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or friendly aliases such as `string`, `integer`, `float`, `boolean`, `object`. Defaults to `VARCHAR`. `ARRAY` is not supported and is rejected with `400`; for multi-value fields declare `VARCHAR` and store the values comma-joined. | +| | `VARCHAR`, `BOOL`, `INT8` to `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or aliases like `string`. Default `VARCHAR`. `ARRAY` is a `400`; comma-join multiple values. | | | Max length for `VARCHAR`. Default `1024`; maximum `65535`. | | | Enables exact-match filtering on this field. (default=`false`) | | | Not supported here: `true` on a new field returns `400`. Dense embeddings can only be declared at [database creation](/api-reference/v2/endpoint/create-tenant). | @@ -145,9 +145,9 @@ Each `add_fields[]` entry uses the same field shape as `database_metadata_schema | Status | When it happens | | --- | --- | -| `400` | Invalid request body, empty `add_fields`, invalid field name or type, `ARRAY` type, more than 32 fields in total, or `enable_dense_embedding` / `enable_sparse_embedding` on a new field. | +| `400` | Invalid body or field; empty `add_fields`; `ARRAY` type; over 32 fields; or embedding flags on a new field. | | `404` | Database not found. | -| `409` | A field with the same name exists with a different definition (or appears twice in `add_fields` with different definitions), or the database changed during the request. | +| `409` | A field with this name exists with a different definition (or repeats in `add_fields`), or the database changed mid-request. | | `500` | Backend persistence or index creation failed. | ## Related diff --git a/api-reference/v2/endpoint/update-source-metadata.mdx b/api-reference/v2/endpoint/update-source-metadata.mdx index bc415b26..59c8e2f1 100644 --- a/api-reference/v2/endpoint/update-source-metadata.mdx +++ b/api-reference/v2/endpoint/update-source-metadata.mdx @@ -105,9 +105,9 @@ const response = await fetch("https://api.hydradb.com/context/policy_main/metada | --- | --- | | | Owning database. (deprecated alias: `tenant_id`) | | | Collection that contains the source. This endpoint does not default it. (deprecated alias: `sub_tenant_id`) | -| | Schema-backed metadata fields to merge into the source's `metadata`. Keys must satisfy the database metadata schema when one exists. (deprecated alias: `tenant_metadata`) | +| | Schema-backed fields merged into the source's `metadata`. Keys must match the schema when one exists. (deprecated alias: `tenant_metadata`) | | | Free-form metadata fields to merge into the source's `additional_metadata`. | -| | Replaces the source's access-control list; it does not merge. Send the complete new list, `[]` to make the source private, or `["__public__"]` to make it visible to every identified caller. Omit it to leave the list unchanged. See [Access control](/essentials/v2/access-control). | +| | Replaces the whole access list: `[]` for private, `["__public__"]` for public, omit to keep. See [Access control](/essentials/v2/access-control). | At least one of `database_metadata`, `additional_metadata` or `acl` is required. @@ -229,7 +229,8 @@ At least one of `database_metadata`, `additional_metadata` or `acl` is required. | Status | When it happens | | --- | --- | -| `400` | Missing `database`, missing `collection`, none of `database_metadata`, `additional_metadata` or `acl` supplied, `document_metadata` supplied, invalid `acl`, unknown database metadata key when a schema exists, wrong type, reserved key, over-size payload, too-deep nesting, or `null` for a dense/sparse-enabled field. | +| `400` | Missing `database` or `collection`; none of `database_metadata`, `additional_metadata` or `acl`; `document_metadata` supplied; or an invalid `acl`. | +| `400` | Unknown schema key, wrong type, reserved key, over-size or too-deep payload, or `null` for a dense/sparse-enabled field. | | `404` | Source does not exist for the `(database, collection, id)` scope. | | `500` | The edit could not be saved. Retry the same edit; it is idempotent. | diff --git a/api-reference/v2/error-responses.mdx b/api-reference/v2/error-responses.mdx index d9082167..22af9be9 100644 --- a/api-reference/v2/error-responses.mdx +++ b/api-reference/v2/error-responses.mdx @@ -64,7 +64,7 @@ Use `error.code` for branching and log `meta.request_id` for every failed reques | `NOT_FOUND` | `404` | The requested context id does not exist in the selected database/collection. | | `SOURCE_PROCESSING` | `409` | A strict-mode delete (`X-HydraDB-Delete-Status: strict`) named a context that is still indexing. Retry after ingestion completes; see the `Retry-After` header. | | `VALIDATION_ERROR` | `422` | The request shape was valid JSON/form data, but one or more fields failed semantic validation. | -| `TENANT_INFRA_NOT_READY` | `422` | The database exists but its infrastructure is still provisioning. Poll [`GET /databases/status`](/api-reference/v2/endpoint/tenant-status) until `data.infra.ready_for_ingestion` is `true`. | +| `TENANT_INFRA_NOT_READY` | `422` | The database is still provisioning. Poll [`GET /databases/status`](/api-reference/v2/endpoint/tenant-status) until `data.infra.ready_for_ingestion` is `true`. | | `RATE_LIMITED` | `429` | The API key exceeded its current rate limit. | | `INTERNAL_ERROR` | `500` | HydraDB hit an unexpected server-side error. | | `SERVICE_UNAVAILABLE` | `503` | A dependency is temporarily unavailable or the service is under load. | @@ -85,7 +85,7 @@ Many storage- and capacity-related ingestion errors are **transient**: the pipel | Code | Meaning | Severity | |---|---|---| -| `E6001` | Vector-store storage/indexing error while persisting processed data. The pipeline retries automatically and it usually clears within minutes. User message: *"Failed to store the processed data. Please try again. If the issue persists, contact support@hydradb.com."* | **Transient** (retryable) | +| `E6001` | Vector-store error while persisting processed data. Retried automatically; usually clears within minutes. | **Transient** (retryable) | `E6001` is **transient**, not terminal. If you observe it on an in-flight context, keep polling [`/context/status`](/api-reference/v2/endpoint/source-status): the context normally advances to `graph_creation` / `completed` on a subsequent retry with no action on your part. Only contact support if the context is still reported as `errored` after retries are exhausted. diff --git a/api-reference/v2/index.mdx b/api-reference/v2/index.mdx index fb7d9b70..fcfefcea 100644 --- a/api-reference/v2/index.mdx +++ b/api-reference/v2/index.mdx @@ -25,7 +25,7 @@ description: "Single reference to all HydraDB endpoints" | Concept | What it means | When you use it | |---|---|---| | `database` | Your isolated workspace for data, metadata schema, and query. | Send it on every API call so HydraDB knows which workspace to read or write. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| `collection` | Optional partition inside a database, often a user, team, account, or customer. | Use it when one database contains data for multiple users or customers. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). Read more about our [multi-tenant architecture](/essentials/v2/databases-and-collections) | +| `collection` | Optional partition inside a database, often a user, team, account, or customer. | For a database holding data for many users or customers. Alias `sub_tenant_id` (deprecated). See [multi-tenancy](/essentials/v2/databases-and-collections) | | [Context](/essentials/v2/ingest) | A `text` or a `conversation`, sent in the `context` list of `POST /context/ingest`. | Everything you ingest. Shared context goes in a shared collection; a person's preferences go in their own. | | `database_metadata_schema` | Database-level fields you define up front so metadata can be filtered or queried consistently. | Use it for stable fields like department, customer, region, plan, category, or compliance label. | | `attributes` | Declared, filterable fields on a context, matching `database_metadata_schema`; `custom_attributes` are free-form. | Send them at ingest; filter with `attributes` on `/query`. | @@ -102,7 +102,7 @@ SDK methods mirror the API: `client..()` maps to the correspondin | [`/databases/stats`](/api-reference/v2/endpoint/tenant-stats) | `GET` | `databases.stats` | Get usage statistics | You want to monitor object counts for a database. | | [`/context/ingest`](/api-reference/v2/endpoint/ingest-context) | `POST` | `context.ingest` | Ingest context | You are sending text or conversations. | | [`/context/status`](/api-reference/v2/endpoint/source-status) | `GET` | `context.status` | Check processing status | You have IDs from ingestion and need to know when they are queryable. | -| [`/context/inspect`](/api-reference/v2/endpoint/fetch-content) | `GET` | `context.inspect` | Read a context's stored content | You need the full stored content behind a `context_id`, such as the context a query chunk came from. For its title and attributes, use `POST /context/list` with `ids`. | +| [`/context/inspect`](/api-reference/v2/endpoint/fetch-content) | `GET` | `context.inspect` | Read a context's stored content | You need the full content behind a `context_id`. For title and attributes, use `POST /context/list` with `ids`. | | [`/context/list`](/api-reference/v2/endpoint/list-documents) | `POST` | `context.list` | Browse context | You need pagination, filters, field projection, or a specific subset by `ids`. | | [`/context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) | `PATCH` | `context.update_source_metadata` | Update a context's metadata | You need to change one existing context's attributes (`database_metadata`) or custom attributes (`additional_metadata`) without re-ingesting. | | [`/context`](/api-reference/v2/endpoint/delete-source) | `DELETE` | `context.delete` | Delete context | You need to remove context by ID. | diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index 8dbf7855..982d5029 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -4,7 +4,7 @@ "connectors.Resource": { "properties": { "acl": { - "description": "Access rule applied to every object synced from this resource: the listed principals (emails or prefixed principals) can read them. Absent means unrestricted. Permissions captured from the provider take precedence where the provider supports them.", + "description": "Principals (emails or prefixed principals) allowed to read objects synced from this resource. Absent means unrestricted; provider permissions take precedence where supported.", "items": { "type": "string" }, @@ -12,11 +12,11 @@ "uniqueItems": false }, "acl_warning": { - "description": "Set when the provider's permissions for this resource could not be captured. The provider's own explanation. While set, objects from this resource are readable by everyone; it clears automatically after the next successful capture.", + "description": "The provider's explanation when this resource's permissions could not be captured. While set, its objects are readable by everyone; clears after the next successful capture.", "type": "string" }, "acl_warning_at": { - "description": "When `acl_warning` last changed (RFC 3339). An unchanged warning keeps its original time, so this reads as \"open since\".", + "description": "When `acl_warning` last changed (RFC 3339). An unchanged warning keeps its original time.", "type": "string" }, "additional_metadata": { @@ -29,12 +29,12 @@ "type": "object" }, "backfill_oldest": { - "description": "Oldest point (RFC 3339) the background fetch of older history has reached so far; it moves back as chunks complete. Empty when that fetch is finished or was not needed.", + "description": "Oldest point (RFC 3339) the background fetch of older history has reached. Empty when that fetch is finished or not needed.", "example": "2026-06-01T00:00:00Z", "type": "string" }, "collection_override": { - "description": "Routes this resource's synced objects into a specific collection, overriding the connector's. Canonical name; mirrors the deprecated `sub_tenant_id_override` alias.", + "description": "Routes this resource's synced objects into a specific collection, overriding the connector's. Formerly `sub_tenant_id_override`.", "type": "string" }, "connector_id": { @@ -43,7 +43,7 @@ "type": "string" }, "custom_instructions": { - "description": "Instructions that steer how documents synced from this resource are ingested and indexed. When set, replaces the connector's `custom_instructions` for this resource; empty inherits the connector's value. Up to 4000 characters; changes apply from the next sync.", + "description": "Ingestion and indexing instructions for this resource, replacing the connector's `custom_instructions`; empty inherits it. Up to 4000 characters, applied from the next sync.", "type": "string" }, "database_override": { @@ -73,7 +73,7 @@ "type": "object" }, "page_acl_warning": { - "description": "Set when restrictions on individual pages inside this resource (for example Confluence pages) could not be resolved, so those pages are readable by everyone. Reported separately from `acl_warning` and cleared after a full sync in which no page fails.", + "description": "Set when page-level restrictions in this resource (for example Confluence pages) could not be resolved, so those pages are readable by everyone. Clears after a clean full sync.", "type": "string" }, "page_acl_warning_at": { @@ -115,7 +115,7 @@ "x-deprecated": "true" }, "sync_blocked": { - "description": "`true` when syncing of this resource has stopped because the provider keeps refusing it, for example a deleted table or a channel the credential cannot access. The resource stays listed; `sync_blocked_reason` says why.", + "description": "`true` when syncing stopped because the provider keeps refusing this resource, for example a deleted table or an inaccessible channel. `sync_blocked_reason` says why.", "example": true, "type": "boolean" }, @@ -244,7 +244,7 @@ "$ref": "#/components/schemas/feedback.GroundTruth" } ], - "description": "What you know the right answer to be. Send an expected `answer`, the `source_ids` that contain it, or both; at least one is required when the field is present. It is a stronger signal than a comment: send it alone and `feedback` becomes optional.", + "description": "The known right answer: an expected `answer`, the `source_ids` that contain it, or both. When sent, `feedback` becomes optional.", "example": { "source_ids": [ "HydraDoc1234", @@ -508,7 +508,7 @@ "type": "boolean" }, "vector_acl_synced": { - "description": "`true` when the new `acl` also reached the search index. If `acl_updated` is `true` and this is absent, principals the edit added may not find the context in search until it is re-indexed; the list is still enforced on every result.", + "description": "`true` when the new `acl` also reached the search index. If absent after an `acl` update, newly added principals may not find the context until it is re-indexed.", "example": true, "type": "boolean" }, @@ -542,7 +542,7 @@ "type": "string" }, "hydration": { - "description": "Set only on context nodes in `auxiliary_relations`, omitted elsewhere. Whether the context is ingested: `resolved` (ingested), `stub` (a relation points at it but it is not ingested yet) or `placeholder` (known only by its provider id, not yet matched to an ingested context).", + "description": "Ingest state, on context nodes in `auxiliary_relations` only: `resolved` (ingested), `stub` (linked to but not ingested yet) or `placeholder` (known only by its provider id).", "type": "string" }, "identifier": { @@ -576,7 +576,7 @@ "graph.GraphRelationsResponse": { "properties": { "auxiliary_relations": { - "description": "The structural graph around the entity relations: where entities appear, comments and attachments, who authored what, and links between contexts. Same entry shape as `relations`, so concatenate the two for one graph. Not counted against `limit` and does not move the cursor.", + "description": "The structural graph around the relations: entity appearances, comments, attachments, authors and context links. Same shape as `relations`; not paged by `limit`.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -719,7 +719,7 @@ }, "properties": { "additionalProperties": {}, - "description": "Your own properties on a relation declared in `forceful_relations` at ingest, exactly as sent. Flat scalar values. Omitted on every other relation and on a forceful relation that declared none.", + "description": "Your properties on a relation declared in `forceful_relations`, exactly as sent. Omitted on other relations and when none were declared.", "type": "object" }, "raw_predicate": { @@ -738,7 +738,7 @@ "type": "string" }, "synthesized": { - "description": "`true` on a relation derived by the API rather than stored: `present_in`, and the `same_thread` and `child_of` relations in a subgraph. Its `relationship_id` is generated. Omitted otherwise.", + "description": "`true` on a relation the API derives rather than stores (`present_in`, and `same_thread` and `child_of` in a subgraph); its `relationship_id` is generated.", "example": true, "type": "boolean" }, @@ -763,7 +763,7 @@ "graph.SourceSubgraphResponse": { "properties": { "auxiliary_relations": { - "description": "The structural graph around the members: which entities appear in them, their comments and attachments, and who authored them. Same entry shape as `relations`.", + "description": "The structural graph around the members: entities, comments, attachments and authors. Same shape as `relations`.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -828,7 +828,7 @@ "type": "string" }, "relations": { - "description": "Relations among the members: declared `relates_to` links whose ends are both members, plus `same_thread` and `child_of` relations for members reached through a shared thread or a hierarchy (marked `synthesized`).", + "description": "Relations among the members: declared `relates_to` links, plus `same_thread` and `child_of` relations (marked `synthesized`) for shared threads and hierarchies.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -933,7 +933,7 @@ "type": "string" }, "hydration": { - "description": "Whether the context is ingested: `resolved` (ingested), `stub` (a relation points at it but it is not ingested yet) or `placeholder` (known only by its provider id, not yet matched to an ingested context).", + "description": "Whether the context is ingested: `resolved` (ingested), `stub` (linked to but not ingested yet) or `placeholder` (known only by its provider id).", "type": "string" }, "source_id": { @@ -1006,7 +1006,7 @@ } }, "truncated": { - "description": "`true` on an `auxiliary_relations` entry whose list was cut at a per-node limit, for example a context with more comments than were returned. Omitted otherwise.", + "description": "`true` on an `auxiliary_relations` entry cut at a per-node limit, for example a context with more comments than were returned.", "example": true, "type": "boolean" } @@ -2520,7 +2520,7 @@ "type": "boolean" }, "webhook_support": { - "description": "True when the provider's data arrives through an inbound webhook rather than scheduled polling. Informational: `credential_schema` already describes what to send.", + "description": "True when the provider's data arrives by inbound webhook instead of scheduled polling. `credential_schema` already describes what to send.", "example": true, "type": "boolean" } @@ -2530,7 +2530,7 @@ "handler.configureReq": { "properties": { "full_visibility_roles": { - "description": "HubSpot only. Names of HubSpot roles whose members can see every record. Requires the account-wide resource (id `all`) in `resources`. Omit to keep the current setting; send `[]` to clear it. An unknown role name fails the request.", + "description": "HubSpot only. HubSpot roles whose members can see every record; requires the `all` resource. Omit to keep the setting, send `[]` to clear it. Unknown roles fail the request.", "items": { "type": "string" }, @@ -2538,7 +2538,7 @@ "uniqueItems": false }, "lookback_days": { - "description": "How far back the first sync fetches historical data, in days. Only applies to the initial sync; later syncs are incremental from the last cursor. Above `30`, some providers fetch the older history in background chunks and the response reports `backfill: true`. Default `30`.", + "description": "Days of history the first sync fetches. Default `30`. Above `30`, some providers fetch older history in background chunks and the response reports `backfill: true`.", "example": 30, "type": "integer" }, @@ -2568,7 +2568,7 @@ "uniqueItems": false }, "table_configs": { - "description": "Per-table replication settings for connectors that sync database tables (currently BigQuery). Saved before the first sync, so the chosen mode applies from the start. Optional; connectors that do not support it return `400`.", + "description": "Per-table replication settings for table-syncing connectors (currently BigQuery), applied from the first sync. Other connectors return `400`.", "items": { "$ref": "#/components/schemas/handler.tableConfigEntry" }, @@ -2666,7 +2666,7 @@ "type": "string" }, "documents_dispatched": { - "description": "Running total of objects sent for ingestion across all completed syncs. It shows that data is moving, not how many documents are indexed: updates count again and deletes are not subtracted.", + "description": "Running total of objects sent for ingestion. It shows data is moving, not the indexed count: updates count again and deletes are not subtracted.", "example": 1, "type": "integer" }, @@ -2690,7 +2690,7 @@ "type": "string" }, "lifecycle": { - "description": "What the connector is doing now, and the status to read: `pending_setup` (no active resources), `ingesting` (data has not finished its first sync), `syncing` (a sync is running), `active`, `paused`, or `reconnect` (credentials were rejected or the connector is blocked; only you can fix it).", + "description": "Current state: `pending_setup` (no active resources), `ingesting` (first sync unfinished), `syncing`, `active`, `paused`, or `reconnect` (credentials rejected or connector blocked).", "type": "string" }, "name": { @@ -2699,7 +2699,7 @@ "type": "string" }, "needs_reauth": { - "description": "True when the provider rejected the connector's OAuth refresh token (expired or revoked). Reconnect the account to resume syncing; the flag clears on the next successful token refresh.", + "description": "True when the provider rejected the OAuth refresh token. Reconnect the account to resume syncing; clears on the next successful token refresh.", "example": true, "type": "boolean" }, @@ -2754,7 +2754,7 @@ "x-deprecated": "true" }, "sync_blocked": { - "description": "True when a failure that retrying cannot fix, such as rejected credentials, stopped scheduled syncs. Updating the credentials or configuration clears it and syncs resume.", + "description": "True when a failure retrying cannot fix, such as rejected credentials, stopped scheduled syncs. Updating credentials or configuration clears it.", "example": true, "type": "boolean" }, @@ -2887,7 +2887,7 @@ "type": "string" }, "documents_dispatched": { - "description": "Running total of objects sent for ingestion across all completed syncs. It shows that data is moving, not how many documents are indexed: updates count again and deletes are not subtracted.", + "description": "Running total of objects sent for ingestion. It shows data is moving, not the indexed count: updates count again and deletes are not subtracted.", "example": 1, "type": "integer" }, @@ -2915,7 +2915,7 @@ "type": "string" }, "lifecycle": { - "description": "What the connector is doing now, and the status to read: `pending_setup` (no active resources), `ingesting` (data has not finished its first sync), `syncing` (a sync is running), `active`, `paused`, or `reconnect` (credentials were rejected or the connector is blocked; only you can fix it).", + "description": "Current state: `pending_setup` (no active resources), `ingesting` (first sync unfinished), `syncing`, `active`, `paused`, or `reconnect` (credentials rejected or connector blocked).", "type": "string" }, "message": { @@ -2929,7 +2929,7 @@ "type": "string" }, "needs_reauth": { - "description": "True when the provider rejected the connector's OAuth refresh token (expired or revoked). Reconnect the account to resume syncing; the flag clears on the next successful token refresh.", + "description": "True when the provider rejected the OAuth refresh token. Reconnect the account to resume syncing; clears on the next successful token refresh.", "example": true, "type": "boolean" }, @@ -2984,7 +2984,7 @@ "x-deprecated": "true" }, "sync_blocked": { - "description": "True when a failure that retrying cannot fix, such as rejected credentials, stopped scheduled syncs. Updating the credentials or configuration clears it and syncs resume.", + "description": "True when a failure retrying cannot fix, such as rejected credentials, stopped scheduled syncs. Updating credentials or configuration clears it.", "example": true, "type": "boolean" }, @@ -3064,7 +3064,7 @@ "$ref": "#/components/schemas/handler.connectorLimitView" } ], - "description": "Present when the organization has used its plan's connector allowance, so creating another connector is refused with `402`. Existing connectors keep syncing. Returned only with `include=health`.", + "description": "Present when the plan's connector allowance is used up, so creating another returns `402`; existing connectors keep syncing. Only with `include=health`.", "example": { "count": 12, "limit": 1, @@ -3109,7 +3109,7 @@ "additionalProperties": { "type": "string" }, - "description": "Map from `connector_id` to a health summary: `healthy`, `degraded`, `failed`, `checking` or `capped`. Returned only with `include=health`. A connector missing from the map has unknown health and should not be shown as failed. `capped` means the organization is at a plan limit; see `plan_cap`.", + "description": "Health per `connector_id`: `healthy`, `degraded`, `failed`, `checking` or `capped` (at a plan limit, see `plan_cap`). Only with `include=health`; missing means unknown.", "type": "object" }, "plan_cap": { @@ -3118,7 +3118,7 @@ "$ref": "#/components/schemas/handler.planCapView" } ], - "description": "Present when the organization is at a plan limit and every connector sync is skipped until the monthly usage resets or the plan changes. Returned only with `include=health`.", + "description": "Present when the organization is at a plan limit, so connector syncs are skipped until usage resets or the plan changes. Only with `include=health`.", "example": { "message": "Success" } @@ -3181,7 +3181,7 @@ "handler.contextMetadataUpdateRequest": { "properties": { "acl": { - "description": "Replaces the context's access-control list; it does not merge. Send the complete new list, `[]` or `null` to make the context private, or `[\"__public__\"]` to make it visible to every identified caller. Omit it to leave the list unchanged.", + "description": "Replaces the context's access-control list. Send the full list, `[]` or `null` for private, or `[\"__public__\"]` for every identified caller. Omit to leave it unchanged.", "items": { "type": "string" }, @@ -3190,7 +3190,7 @@ }, "additional_metadata": { "additionalProperties": {}, - "description": "Free-form values to merge into the context's `custom_attributes` (named `additional_metadata` on older responses). At most 1 KiB for the whole map, measured as compact UTF-8 JSON; over the cap returns 400.", + "description": "Free-form values merged into the context's `custom_attributes`. At most 1 KiB as compact JSON.", "example": { "author": "ada", "doc_version": 3 @@ -3209,7 +3209,7 @@ }, "database_metadata": { "additionalProperties": {}, - "description": "Values to merge into the context's `attributes` (named `metadata` on older responses). Keys must be declared in the database schema when it has one. At most 16 KiB for the whole map, measured as compact UTF-8 JSON; over the cap returns 400.", + "description": "Values merged into the context's `attributes`. Keys must be declared in the schema when there is one. At most 16 KiB as compact JSON.", "example": { "department": "legal", "priority": 7 @@ -3434,7 +3434,7 @@ }, "additional_metadata": { "additionalProperties": {}, - "description": "Key-value pairs merged into the custom attributes of every object synced from this resource. Limited to 1 KiB of compact JSON (UTF-8 bytes, whole map); the limit is applied when synced objects are ingested, not to this request.", + "description": "Key-value pairs merged into the custom attributes of every synced object. Up to 1 KiB of compact JSON, checked when synced objects are ingested.", "example": { "author": "ada", "doc_version": 3 @@ -3446,7 +3446,7 @@ "type": "string" }, "custom_instructions": { - "description": "Instructions that steer how documents synced from this resource are ingested and indexed. When set, replaces the connector's `custom_instructions` for this resource; empty inherits the connector's value. Up to 4000 characters.", + "description": "Ingestion and indexing instructions for this resource, replacing the connector's `custom_instructions`; empty inherits it. Up to 4000 characters.", "type": "string" }, "database_override": { @@ -3468,7 +3468,7 @@ }, "metadata": { "additionalProperties": {}, - "description": "Key-value pairs merged into the attributes of every object synced from this resource. Limited to 16 KiB of compact JSON (UTF-8 bytes, whole map); the limit is applied when synced objects are ingested, not to this request.", + "description": "Key-value pairs merged into the attributes of every synced object. Up to 16 KiB of compact JSON, checked when synced objects are ingested.", "example": { "department": "finance", "priority": 7 @@ -3534,7 +3534,7 @@ "handler.resourceMapping": { "properties": { "acl": { - "description": "Restricts every object synced from this resource to the listed principals (emails or prefixed principals, as in the query-side `user_email`). Omitted means unrestricted; an empty list makes the objects private. See Access Control.", + "description": "Principals (emails or prefixed principals) allowed to read objects synced from this resource. Omitted means unrestricted; an empty list makes them private.", "items": { "type": "string" }, @@ -3543,7 +3543,7 @@ }, "additional_metadata": { "additionalProperties": {}, - "description": "Key-value pairs merged into the custom attributes of every object synced from this resource. Free-form, no schema required. Provider-generated fields win on conflict.", + "description": "Free-form key-value pairs merged into the custom attributes of every synced object. Provider-generated fields win on conflict.", "example": { "author": "ada", "doc_version": 3 @@ -3556,7 +3556,7 @@ "type": "string" }, "custom_instructions": { - "description": "Instructions that steer how documents synced from this resource are ingested and indexed. When set, replaces the connector's `custom_instructions` for this resource; empty inherits the connector's value. Up to 4000 characters.", + "description": "Ingestion and indexing instructions for this resource, replacing the connector's `custom_instructions`; empty inherits it. Up to 4000 characters.", "type": "string" }, "database": { @@ -3566,7 +3566,7 @@ }, "metadata": { "additionalProperties": {}, - "description": "Key-value pairs merged into the attributes of every object synced from this resource. Undeclared keys are stored, but only keys declared in the database's metadata schema are indexed for filtering. `connector_id` and `provider` always win on conflict.", + "description": "Key-value pairs merged into the attributes of every synced object. Only keys declared in the metadata schema are filterable. `connector_id` and `provider` win on conflict.", "example": { "department": "finance", "priority": 7 @@ -3596,7 +3596,7 @@ "x-deprecated": "true" }, "sync_mode": { - "description": "How this resource picks up changes, for providers that support a choice (currently Attio objects and lists). `rescan` (default) re-reads everything each sync, so edits are seen. `new_only` reads only new records each sync and does not pick up edits.", + "description": "How this resource picks up changes (currently Attio objects and lists): `rescan` (default) re-reads everything and sees edits; `new_only` reads only new records.", "enum": [ "rescan", "new_only" @@ -3675,7 +3675,7 @@ "handler.tableConfigEntry": { "properties": { "change_history": { - "description": "Read changes from BigQuery's own change history instead of a column: `appends` (new rows only) or `changes` (inserts, updates and deletes). Set exactly one of `replication_key` or `change_history`.", + "description": "Read changes from BigQuery's change history instead of a column: `appends` (new rows only) or `changes` (inserts, updates, deletes). Set this or `replication_key`.", "type": "string" }, "replication_key": { @@ -3887,7 +3887,7 @@ }, "relations_error": { "deprecated": true, - "description": "Deprecated: returned only for the deprecated `documents` and `app_knowledge` fields. Why this entry's forceful relations could not be created; the entry is still queued.", + "description": "Deprecated: only for `documents` and `app_knowledge`. Why this entry's forceful relations failed; the entry is still queued.", "type": "string" }, "status": { @@ -3924,7 +3924,7 @@ "type": "string" }, "indexing_status": { - "description": "Processing state: `queued`, `processing`, `graph_creation`, `completed` or `errored`. A context is searchable from `graph_creation` on; `completed` and `errored` are terminal.", + "description": "`queued`, `processing`, `graph_creation`, `completed` or `errored`. Searchable from `graph_creation` on; `completed` and `errored` are final.", "example": "completed", "type": "string" }, @@ -3963,7 +3963,7 @@ }, "source_fields": { "additionalProperties": {}, - "description": "Match on built-in fields: `title` (case-insensitive prefix), `type`, `description`, `url`, `timestamp`, `app_provider`, `app_kind`, `app_external_id`, `app_parent_id`. Pair `app_external_id` or `app_parent_id` with `app_provider`; they are only unique per provider.", + "description": "Match on built-in fields: `title` (case-insensitive prefix), `type`, `description`, `url`, `timestamp` and `app_*` fields. Pair `app_external_id` or `app_parent_id` with `app_provider`.", "type": "object" } }, @@ -4468,7 +4468,7 @@ "additionalProperties": { "$ref": "#/components/schemas/ingestion.GraphPayload" }, - "description": "Your own graph for contexts in this request, keyed by `context_id`. HydraDB uses it instead of extracting a graph from that context, which is still chunked and embedded. A key that matches no `context_id` in the request is a `400`.", + "description": "Your own graph for contexts in this request, keyed by `context_id`, used instead of extracted entities; the text is still chunked and embedded. Unknown keys are a `400`.", "type": "object" }, "instructions": { @@ -4533,7 +4533,7 @@ "memories.IngestItem": { "properties": { "acl": { - "description": "Principals allowed to retrieve the context: bare emails or `user_email:`, `group:` and `domain:` principals, or `__public__`. Omit it for unrestricted; send `[]` for nobody. A malformed principal rejects the whole request with `400`.", + "description": "Principals allowed to retrieve the context: emails, `user_email:`, `group:` or `domain:` principals, or `__public__`. Omit for unrestricted, `[]` for nobody.", "items": { "type": "string" }, @@ -4546,7 +4546,7 @@ "type": "object" }, "context_category": { - "description": "Label for what the context holds: `user_preference`, `business_knowledge` or `decision_trace`. `auto`, the default, sets no label. Any other value is a `400`.", + "description": "Label for what the context holds: `user_preference`, `business_knowledge` or `decision_trace`. Default `auto` sets no label.", "enum": [ "auto", "user_preference", @@ -4560,7 +4560,7 @@ "type": "string" }, "conversation": { - "description": "Turns of `{role, content}`, the shape chat model APIs use. Send exactly one of `text` or `conversation`. Needs at least one `user` or `assistant` turn; the speaker is the context's `user_name`.", + "description": "Turns of `{role, content}`, as chat model APIs use. Send exactly one of `text` or `conversation`. Needs at least one `user` or `assistant` turn.", "example": [ { "content": "# Q4 Report\n\nRevenue grew 23% quarter over quarter." @@ -4603,7 +4603,7 @@ "type": "string" }, "title": { - "description": "Readable name for the context. Trimmed, then at most 1,024 bytes of UTF-8. Two contexts with identical text and no `context_id` are told apart by their title.", + "description": "Readable name for the context, at most 1,024 bytes after trimming. Tells apart contexts with identical text and no `context_id`.", "example": "Project Phoenix Overview", "type": "string" }, @@ -4688,7 +4688,7 @@ "type": "string" }, "received_at": { - "description": "When the context this chunk belongs to was received (RFC 3339). This is the ingest time, not the caller's happened_at, which is not echoed here. Omitted when the store holds no receipt time for the row (older rows); it is never sent empty.", + "description": "When the chunk's context was received (RFC 3339): the ingest time, not `happened_at`. Omitted on older rows that have none.", "type": "string" }, "score": { @@ -4778,7 +4778,7 @@ "type": "string" }, "timestamp": { - "description": "When the relation was introduced (the date of the source it was extracted from), in Unix epoch seconds, possibly fractional. Omitted when the relation has none.", + "description": "When the relation was introduced (its source's date), in Unix epoch seconds, possibly fractional. Omitted when unknown.", "type": [ "number", "null" @@ -4861,7 +4861,7 @@ "search.QueryRequest": { "properties": { "acl": { - "description": "Query on behalf of an identity: results are limited to context these principals may retrieve, plus public and unrestricted context. Entries are emails or principals such as `user_email:`, `group:` or `domain:`. Omit it, or send `[]` or `[\"*\"]`, for no access scoping. An unrecognized entry matches only public and unrestricted context.", + "description": "Principals to query as: emails or `user_email:`, `group:` or `domain:` principals. Returns their context plus public and unrestricted context. Omit, `[]` or `[\"*\"]` for no scoping.", "items": { "type": "string" }, @@ -4878,7 +4878,7 @@ }, "attributes": { "additionalProperties": {}, - "description": "Filter on the database's declared attributes with an operator query: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$exists`. Applies to chunks, forceful relations and graph paths alike, and is ANDed with `metadata_filters` when both are sent. It cannot filter `custom_attributes`.", + "description": "Operator filter on declared attributes: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$exists`. Cannot filter `custom_attributes`.", "type": "object" }, "code_search": { @@ -4892,7 +4892,7 @@ "type": "string" }, "collections": { - "description": "Preferred /query scope selector. Send either a list of collection IDs for equal normalized weighting, or an object mapping collection ID to a positive relative ranking weight with at most one decimal place. Do not send together with the deprecated sub_tenant_ids or sub_tenant_id.", + "description": "Collections to search: a list for equal weighting, or an object mapping collection ID to a positive ranking weight (one decimal place). Do not combine with `sub_tenant_ids`.", "example": [ "team_docs", "engineering" @@ -4945,7 +4945,7 @@ "type": "boolean" }, "ids": { - "description": "Restrict retrieval to these `context_id`s, at most 200. A scoped query that matches nothing returns nothing rather than widening to the whole scope.", + "description": "Restrict retrieval to these `context_id`s, at most 200. If none match, the result is empty.", "example": [ "HydraDoc1234", "HydraDoc4567" @@ -4993,7 +4993,7 @@ "type": "string" }, "profile_subject": { - "description": "Name of an entity whose compiled profile is added to `llm_prompt` under `## Profiles`. Never changes which chunks are returned. Omit it for no requested profile. Has no effect where entity profiles are not enabled.", + "description": "Entity whose compiled profile is added to `llm_prompt` under `## Profiles`. Does not change which chunks are returned.", "type": "string" }, "query": { @@ -5019,7 +5019,7 @@ "x-deprecated": "true" }, "recency_bias": { - "description": "Recency boost applied to ranking, from `0.0` to `1.0`. Default `0.4`: it reorders results within a relevance gap of up to 0.4 but never buries a clearly more relevant result. Send `0` to disable recency; higher values favour newer content more strongly.", + "description": "Recency boost for ranking, from `0.0` to `1.0`. Default `0.4`; `0` disables it. It never buries a clearly more relevant result.", "example": 0.2, "type": "number" }, @@ -5071,11 +5071,11 @@ "x-deprecated-since": "2.0.1" }, "temporal_now": { - "description": "The time to treat as now for temporal reasoning, in ISO 8601. Set it when replaying past conversations or backfilling; otherwise durations to now and recency windows use the server's clock.", + "description": "The time to treat as now for temporal reasoning (ISO 8601), for example when replaying past conversations. Default: the server's clock.", "type": "string" }, "temporal_reasoning": { - "description": "Resolve time-based questions (current, as of, ranges, upcoming) and return the matching dated facts in `chunks[].temporal` and `llm_prompt`. Never changes which chunks are returned. Default `true`; send `false` to disable.", + "description": "Resolve time-based questions and return the matching dated facts in `chunks[].temporal` and `llm_prompt`. Does not change which chunks are returned. Default `true`.", "example": true, "type": "boolean" }, @@ -5087,7 +5087,7 @@ "x-deprecated": "true" }, "titles": { - "description": "Optional exact document-title filter. Values are matched case-insensitively and ORed, resolved to source IDs, then the normal query pipeline runs within that source scope. When ids is also supplied, the two filters are intersected.", + "description": "Exact document titles to search within, matched case-insensitively and ORed. Intersected with `ids` when both are sent.", "items": { "type": "string" }, @@ -5111,7 +5111,7 @@ "description": "The four-key /query response body: chunks, graph, forceful_relations and llm_prompt, and nothing else.", "properties": { "chunks": { - "description": "Retrieved chunks, ranked. Each carries its own text, enrichment and received_at, and nothing else about its source: POST /context/list with its context_id in `ids` returns the source's title, type, collection and metadata.", + "description": "Retrieved chunks, ranked, each with its text, enrichment and `received_at`. For its source's title and metadata, pass the `context_id` to `POST /context/list` in `ids`.", "items": { "$ref": "#/components/schemas/search.QueryChunk" }, @@ -5132,7 +5132,7 @@ "type": "array" }, "llm_prompt": { - "description": "The whole response rendered as markdown to inject verbatim into a model call: results, forceful relations, related facts (`[P1]`, `[P2]`, ... in `graph` order), temporal facts and sources. It also carries what no JSON key does: a computed duration, source facts, entity profiles and code-search answers. `\"\"` only when the query found nothing.", + "description": "The whole response as markdown to pass verbatim to a model, including facts no JSON key carries (durations, entity profiles, code-search answers). `\"\"` only when nothing matched.", "type": "string" } }, @@ -5385,7 +5385,7 @@ "type": "string" }, "type": { - "description": "Storage layout the database was created with. `split` means separate knowledge and memory corpora, selected by `type` on each call. Absent where the layout is not exposed.", + "description": "Storage layout the database was created with; `split` means separate knowledge and memory corpora. Absent where the layout is not exposed.", "enum": [ "split" ], @@ -5472,12 +5472,12 @@ "type": "boolean" }, "ready_for_ingestion": { - "description": "True once the database is fully provisioned (`scheduler_status`, `graph_status` and both `vectorstore_status` corpora ready) and can accept ingestion and serve queries. Creation is asynchronous, so poll GET /databases/status until this is true before ingesting or querying.", + "description": "True once the database is fully provisioned and can accept ingestion and queries. Creation is asynchronous: poll `GET /databases/status` until this is true.", "example": true, "type": "boolean" }, "scheduler_status": { - "description": "Whether lifecycle provisioning has finished for this database (creation_status is ready). False while the database is still being created, even if individual collections already exist.", + "description": "`true` once provisioning of this database has finished; `false` while it is still being created, even if collections already exist.", "example": true, "type": "boolean" }, @@ -5601,7 +5601,7 @@ "type": "string" }, "database_metadata_schema": { - "description": "Defines database-level metadata fields for exact-match filtering and semantic/BM25 search. Canonical name; `tenant_metadata_schema` is a deprecated alias. Schema field names are immutable after database creation.", + "description": "Database-level attribute fields for exact-match filtering and semantic or BM25 search. Field names cannot change after creation.", "example": [ { "data_type": "VARCHAR", @@ -6014,12 +6014,12 @@ "uniqueItems": false }, "generate_signing_secret": { - "description": "Generate a signing secret as part of this request, so registering and enabling signing are one atomic operation. The secret is returned once on the response and cannot be retrieved later. Mutually exclusive with `signing_secret`.", + "description": "Generate a signing secret in this request. It is returned once and cannot be retrieved later. Cannot be combined with `signing_secret`.", "example": true, "type": "boolean" }, "signing_secret": { - "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=\u003chex\u003e`, the HMAC-SHA256 of the raw request body keyed by this secret. Minimum 16 characters when you supply your own; omit it and one is generated for you. On registration, omitting this field preserves any existing secret - to disable signing, call DELETE /webhooks/indexing/signing-secret.", + "description": "Secret, at least 16 characters, that signs deliveries: `X-HydraDB-Signature: sha256=\u003chex\u003e` is the HMAC-SHA256 of the raw body. Omit it to keep any existing secret.", "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", "type": "string" }, @@ -6055,7 +6055,7 @@ "type": "boolean" }, "signing_secret": { - "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=\u003chex\u003e`, the HMAC-SHA256 of the raw request body keyed by this secret. Minimum 16 characters when you supply your own; omit it and one is generated for you. On registration, omitting this field preserves any existing secret - to disable signing, call DELETE /webhooks/indexing/signing-secret.", + "description": "Secret that signs deliveries: `X-HydraDB-Signature: sha256=\u003chex\u003e` is the HMAC-SHA256 of the raw request body keyed by it.", "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", "type": "string" }, @@ -6238,7 +6238,7 @@ }, "/connectors/providers": { "get": { - "description": "Without `id`: lists every provider you can connect, with its category, maturity and display order. With `id=\u003cprovider\u003e`: describes that provider: the object types that become searchable, the searchable and filterable fields (each with the `filter_key` to use in a query filter), the `credential_schema` for connecting it, and a `setup_guide` when setup needs more than credentials.", + "description": "Lists the providers you can connect. With `id`, describes one: its searchable object types and fields (with `filter_key`), `credential_schema`, and `setup_guide` when needed.", "parameters": [ { "description": "Provider name (e.g. slack, gmail). Omit to list all.", @@ -6480,7 +6480,7 @@ }, "/connectors/{id}/discover": { "get": { - "description": "List a connected provider's resources using the connector's stored credentials. Passing cursor or limit opts into pagination (currently Notion only): the response then adds next_cursor and has_more, a page may hold fewer than limit resources, and clients must continue while has_more is true. Without either param the full resource list is returned.", + "description": "List a connected provider's resources using the connector's stored credentials. `cursor` or `limit` paginates (currently Notion only); continue while `has_more` is `true`.", "parameters": [ { "description": "Connector ID", @@ -6885,7 +6885,7 @@ }, "/context": { "delete": { - "description": "Delete one or more contexts by ID. By default every outcome answers `200`, including a delete that removed nothing, so check `data.deleted_count` and `data.results`. Send `X-HydraDB-Delete-Status: strict` to get `404`, `409` or `500` when the delete did not happen; the body still carries the same `results` and `deleted_count`. Strict is recommended for new integrations and is expected to become the default.", + "description": "Delete contexts by ID. By default every outcome is `200`, so check `deleted_count` and `results`. Send `X-HydraDB-Delete-Status: strict` for `404`, `409` or `500` on failure.", "parameters": [ { "description": "`strict` answers `404`, `409` or `500` when the delete did not happen; `legacy` always answers `200`. Omit it for the server default, currently `legacy`.", @@ -6978,7 +6978,7 @@ }, "/context/ingest": { "post": { - "description": "Ingest context into a database. Send a `context` list, where each context is a `text` or a `conversation`, as an application/json body or as the `context` field of a multipart form; both run the same validation. The response is `202`: the contexts are queued, not yet indexed. Poll `GET /context/status` with the returned ids.", + "description": "Ingest contexts, each a `text` or a `conversation`, as a JSON body or a multipart `context` field. Returns `202` once queued; poll `GET /context/status` with the returned ids.", "requestBody": { "content": { "application/json": { @@ -7002,7 +7002,7 @@ "type": "string" }, "context": { - "description": "JSON array of contexts, the same list the JSON body carries under `context`. Each context is exactly one of `text` or `conversation`. At most 100 contexts, 1 MiB of text per context and 8 MiB per request. Unknown keys are a `400`.", + "description": "JSON array of contexts, the same list the JSON body sends as `context`. Each is one of `text` or `conversation`. At most 100 contexts, 1 MiB per context, 8 MiB per request.", "title": "context", "type": "string" }, @@ -7294,7 +7294,7 @@ }, "/context/list": { "post": { - "description": "List the context in a database or collection, ingested and synced by connectors, one page at a time. Each row carries the context's `id` and its metadata; fetch its content with `GET /context/inspect`.", + "description": "List the contexts in a database or collection, ingested or synced, one page at a time. Fetch a context's content with `GET /context/inspect`.", "requestBody": { "content": { "application/json": { @@ -7343,7 +7343,7 @@ }, "/context/relations": { "get": { - "description": "Return the entity relations extracted from one context, or from every context in the collection when `id` is omitted, with the structural graph around them. `limit` and `cursor` page through `relations`.", + "description": "Return the entity relations extracted from one context, or from the whole collection when `id` is omitted, with the structural graph around them. Paged by `limit` and `cursor`.", "parameters": [ { "description": "Database to read. Required.", @@ -7431,7 +7431,7 @@ } }, { - "description": "Principals to answer as. Only results they may see are returned. Repeat the parameter (`acl=a\u0026acl=b`) or send a comma-separated list. Omit it for no access-control scoping.", + "description": "Principals to answer as; only results they may see are returned. Repeat the parameter or send a comma-separated list. Omit it for no scoping.", "in": "query", "name": "acl", "schema": { @@ -7587,7 +7587,7 @@ }, "/context/subgraph": { "get": { - "description": "Return the connected subgraph of one context: every context reachable from it through context-level relations (declared `relates_to` links, a shared thread, parent and child), breadth-first up to `depth` hops, the relations among those members, and the structural graph around them (entities, comments, attachments, authors). Chunk-level entity relations are not included; use Inspecting Context Relations for those. An unknown id returns an empty subgraph, not an error.", + "description": "Return the contexts reachable from one context through context-level relations, up to `depth` hops, with their relations and surrounding graph. An unknown id returns an empty subgraph.", "parameters": [ { "description": "The `context_id` to start from. This form takes any id, including one that contains `/`.", @@ -7681,7 +7681,7 @@ } }, { - "description": "Principals to answer as. The subgraph contains only contexts they may see, checked at every hop. Repeat the parameter (`acl=a\u0026acl=b`) or send a comma-separated list. Omit it for no access-control scoping.", + "description": "Principals to answer as; the subgraph holds only contexts they may see, checked at every hop. Repeat the parameter or send a comma-separated list.", "in": "query", "name": "acl", "schema": { @@ -7730,7 +7730,7 @@ }, "/context/{id}/metadata": { "patch": { - "description": "Merge attribute and custom attribute values, or replace the access-control list, of one existing context without re-ingesting it. Keys in the request are inserted or overwritten; keys not sent are kept. `database` and `collection` are required.", + "description": "Update one context without re-ingesting it: merge its attributes and custom attributes, or replace its access-control list. `database` and `collection` are required.", "parameters": [ { "description": "`context_id` of the context to update.", @@ -8372,7 +8372,7 @@ }, "/feedback": { "post": { - "description": "Record feedback about a query that already ran, correlated by the `request_id` returned in that query's `response.meta.request_id`. Accepts a free-text comment plus an optional positive/negative/neutral rating, and is intended for both end users and agents (`source`). Feeds internal retrieval-quality validation; it does not change the result of the original query.", + "description": "Record feedback on a query that already ran, linked by its `request_id`: a comment, an optional rating, a `ground_truth`, or a mix. It does not change the original result.", "requestBody": { "content": { "application/json": { @@ -8441,7 +8441,7 @@ }, "/query": { "post": { - "description": "Search a database and return ranked chunks, graph paths, forceful relations and `llm_prompt`, a prompt-ready rendering of all of them. Scope with `collection` or `collections`, filter with `attributes`, or restrict the search to known contexts with `ids` or `titles`.", + "description": "Search a database and return ranked chunks, graph paths, forceful relations and `llm_prompt`. Scope with `collection` or `collections`; narrow with `attributes`, `ids` or `titles`.", "requestBody": { "content": { "application/json": { @@ -8571,7 +8571,7 @@ "x-fern-sdk-method-name": "get" }, "post": { - "description": "Register the indexing webhook for this API key's org. Set `generate_signing_secret` to register and enable signing in one request; the secret is returned once on the response. Omitting `signing_secret` preserves any secret already configured - to disable signing, call DELETE /webhooks/indexing/signing-secret.", + "description": "Register your organization's indexing webhook. Omitting `signing_secret` keeps an existing secret; disable signing with `DELETE /webhooks/indexing/signing-secret`.", "requestBody": { "content": { "application/json": { diff --git a/essentials/v2/access-control.mdx b/essentials/v2/access-control.mdx index 1fdb0f84..89875c1c 100644 --- a/essentials/v2/access-control.mdx +++ b/essentials/v2/access-control.mdx @@ -31,7 +31,7 @@ A principal is one string identifying who may retrieve a document. Five forms: | Principal | Meaning | |---|---| | `user_email:grace@acme.com` | One person, by email. A bare `grace@acme.com` is accepted and normalized to this form. | -| `domain:acme.com` | Everyone whose email is under that domain. Matches automatically for any caller who queries with an email at that domain; you do not have to declare it on the query side. | +| `domain:acme.com` | Everyone with an email under that domain. Matches any caller querying with such an email, with no query-side declaration. | | `group::` | A group in the source app, for example `group:slack:C0123` or `group:google:eng@acme.com`. | | `__public__` | Every identified caller in the collection. | | `__private__` | Nobody who queries with an `acl`. The stored form of an explicitly empty allow-list. | @@ -125,8 +125,8 @@ For supported providers, HydraDB reads the source app's own permissions on every | Provider | What is captured | |---|---| | **Slack** | Public channels are visible workspace-wide; private channels only to their members, resolved to member emails. | -| **Google Drive** | Per-file sharing: user, group, domain, and public grants. Permission-only changes (a share with no edit to the file) are picked up through the Drive changes feed, which content sync alone cannot see. | -| **GitHub** | Repository visibility. Private repos additionally capture the collaborator list when every collaborator has a visible public email; otherwise your resource rule governs. | +| **Google Drive** | Per-file sharing: user, group, domain and public grants. Permission-only changes are picked up through the Drive changes feed. | +| **GitHub** | Repository visibility. Private repos also capture collaborators when all have a public email; otherwise your resource rule governs. | | **Confluence** | Space-level view permissions, with groups expanded to member emails, plus per-page view restrictions. | | **Jira** | Who holds Browse access per project, plus per-issue security levels. | diff --git a/essentials/v2/api-results.mdx b/essentials/v2/api-results.mdx index 394eb963..4bf192de 100644 --- a/essentials/v2/api-results.mdx +++ b/essentials/v2/api-results.mdx @@ -143,15 +143,15 @@ FAQ: refunds to a card take 5 to 7 business days to appear. | Section | Built from | Labels | | --- | --- | --- | -| `# Query results` | The query; an `**Interpreted:**` line when an alias or a resolved reference widened it; a `**Found:**` line counting what follows; a `**Note:**` line when a temporal, source or profile lookup degraded or was truncated; and, when there is a result, the instruction to cite it by its number | None | -| `## Results` | `chunks[]`, in ranked order: a `### 1. title` heading, a line with relevance (`score`), collection, type and category (`enrichment_kind`), a line with the id (`context_id`) and last-updated date, the `content`, then `**Enrichment:**` (`enrichment`). Results are separated by `---`. | `[1]`, `[2]`, ... | -| `## Forceful relations` | `forceful_relations[]`, the context the hits declared with `forceful_relations` at ingest: a guide line, then `### R1. title` blocks laid out like results, with `**Linked from:**` (`via.from`) in place of relevance | `[R1]`, `[R2]`, ... | -| `## Related facts` | `graph[]`, one line per path: its chain of hops (`**A** -pred→ **B**`), its relevance after reranking in parentheses when it has one (`(relevance 0.81)`; a path with no reranked score has no parenthetical), and the results its hops were extracted from, with the `path_summary` indented under it unless it only narrates the chain | `[P1]`, `[P2]`, ... in `graph[]` order; each line also cites its results | -| `## Temporal facts` | For a "how long between" question, a `**Duration:**` line first (the computed days, whether approximate, and the two dated facts). Then the dated facts behind `chunks[].temporal`, with window, fact type, precision and status, then the evidence phrase after a `;` | None; each fact cites its result, or names its source id when that chunk is not a result | -| `## Source facts` | App-native facts about the sources behind the results: who acted and in what role, where, which thread and connector, when synced. Prompt only: no JSON key carries them | None; each fact cites its result | -| `## Profiles` | The entity profiles the query selected, one `### name` block each. Prompt only | None | -| `## Code search` | The repository code-search answer, one `### repository` block each, with its status. Prompt only | None | -| `## Sources` | Each context once, in order of first appearance: title, type, id, url (web links only, never a storage location such as `s3://...`) and last-updated date | None; the numbers count contexts, not results | +| `# Query results` | The query, then `**Interpreted:**`, `**Found:**` and `**Note:**` lines when they apply, and the instruction to cite results by number | None | +| `## Results` | `chunks[]` in ranked order: `### 1. title`, relevance, collection, type, id, last updated, `content`, `**Enrichment:**` | `[1]`, `[2]`, ... | +| `## Forceful relations` | `forceful_relations[]` as `### R1. title` blocks, with `**Linked from:**` (`via.from`) in place of relevance | `[R1]`, `[R2]`, ... | +| `## Related facts` | `graph[]`, one line per path: its hops, its relevance when it has one, and the results it cites | `[P1]`, `[P2]`, ... in `graph[]` order | +| `## Temporal facts` | An optional `**Duration:**` line, then the dated facts behind `chunks[].temporal` | None; each fact cites its result or source id | +| `## Source facts` | App-native facts: actor, role, place, thread, connector, sync time. Prompt only | None; each fact cites its result | +| `## Profiles` | The selected entity profiles, one `### name` block each. Prompt only | None | +| `## Code search` | The code-search answer, one `### repository` block each. Prompt only | None | +| `## Sources` | Each context once: title, type, id, url (web links only) and last-updated date | None; numbers count contexts, not results | A section with nothing in it is left out, and only a query that finds nothing at all gets an empty `llm_prompt`. A path in `## Related facts` that carries a decision trace has an indented `**Decision:**` line under it. Ask the model to cite the labels and you get answers you can trace: a `[1]` in the reply is result 1, whose `**Id:**` is `refund-policy`, which you can look up with [`POST /context/list`](/api-reference/v2/endpoint/list-documents) or open with [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content); a `[P1]` is the first path in `graph[]`. How a multi-hop chain reads is on [Query](/essentials/v2/query#llm_prompt). @@ -164,11 +164,11 @@ Render a UI, rerank, or apply your own rules from the three structured keys. The | Read | For | | --- | --- | | `chunks[].content` | The matched text, verbatim. | -| `chunks[].enrichment` | What enrichment extracted from that chunk (a preference, a fact), as a string. | +| `chunks[].enrichment` | The statement extracted from that chunk, as a string. | | `chunks[].score` | Relevance, for your own thresholds. | -| `chunks[].received_at` | When HydraDB received the context, as an RFC 3339 timestamp; omitted when none is recorded. Not its `happened_at`. | -| `graph[].path_summary` | One sentence per graph path; `graph[].triplets` for the steps and `graph[].origin` for how it was found. To show a path under its chunk, see [Attaching graph paths to chunks](/essentials/v2/query#attaching-graph-paths-to-chunks). | -| `forceful_relations[]` | Chunks linked at ingest with `forceful_relations`, each with the `via` link that brought it in. | +| `chunks[].received_at` | When HydraDB received the context, RFC 3339; omitted when none is recorded. | +| `graph[].path_summary` | One sentence per path. See [Attaching graph paths to chunks](/essentials/v2/query#attaching-graph-paths-to-chunks). | +| `forceful_relations[]` | Chunks linked at ingest, each with its `via` link. | ```python Python SDK diff --git a/essentials/v2/attributes.mdx b/essentials/v2/attributes.mdx index ba226b24..b47527b0 100644 --- a/essentials/v2/attributes.mdx +++ b/essentials/v2/attributes.mdx @@ -131,9 +131,9 @@ await client.databases.create({ | Field | Type / values | Purpose | | --- | --- | --- | -| `name` | string | The attribute key. Must start with a letter and contain only letters, numbers and underscores, at most 255 characters. Reserved system names such as `chunk_id`, `source_id`, `source_title` and `description` are rejected; the error lists every reserved name. | -| `data_type` | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the friendly aliases `string`, `boolean`, `integer`, `float`, `object` | Defaults to `VARCHAR`. `array` is **not** supported and is rejected with `400`; see [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | -| `max_length` | integer | Maximum length of a `VARCHAR` value. Default `1024`, maximum `65535`. It sizes one field and cannot be raised later, so declare it large enough up front. It is **not** the budget for all attributes on one context; for that see [Size limits](#size-limits). | +| `name` | string | The attribute key: a letter, then letters, numbers or underscores, at most 255 characters. Reserved system names such as `source_id` are rejected. | +| `data_type` | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the aliases `string`, `boolean`, `integer`, `float`, `object` | Default `VARCHAR`. `array` is rejected with `400`; see [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | +| `max_length` | integer | Maximum length of one `VARCHAR` value. Default `1024`, maximum `65535`; cannot be raised later. | | `enable_match` | boolean | Turns on keyword matching (a text analyzer) for the field. An `attributes` filter works on every declared field whether or not this is set. | | `enable_dense_embedding` | boolean | Adds dense semantic search over a `VARCHAR` field. | | `enable_sparse_embedding` | boolean | Adds sparse (BM25) keyword search over a `VARCHAR` field. | @@ -388,10 +388,10 @@ Operators combine and nest: | Several fields in one object | AND. Every clause must match. | | Several operators on one field | AND. `{"priority": {"$gte": 3, "$lte": 7}}` is a range. | | Equality | Exact, against the **whole** stored value. Strings are case-sensitive. | -| Value types | Every operand must match the field's declared type: a string for `VARCHAR`, `true` or `false` for `BOOL`, a whole number for the integer types, a number for `FLOAT` and `DOUBLE`. `{"priority": "7"}` on an `INT64` field is a `400`, not an empty result. | +| Value types | Each operand must match the field's declared type. `{"priority": "7"}` on an `INT64` field is a `400`, not an empty result. | | `JSON` fields | Only `$exists` applies. Any other operator on a `JSON` field is a `400`. | -| Missing values | A context with no value for a field never matches a comparison on that field. `$ne`, `$nin` and `$not` exclude it too. To keep such context, say so: `{"$or": [{"region": {"$ne": "eu"}}, {"region": {"$exists": false}}]}`. | -| Field names | Must be declared in `database_metadata_schema`. An undeclared field is a `400` (`unknown attribute`), never silently ignored. A reserved system column is a `400`. On a database created without any schema, every field is compared as a string. | +| Missing values | A context with no value for a field never matches a comparison on it, including `$ne`, `$nin` and `$not`. Add `{"$exists": false}` in an `$or` to keep it. | +| Field names | Must be declared in `database_metadata_schema`; an undeclared or reserved field is a `400`. Without any schema, every field compares as a string. | | Custom attributes | Cannot be filtered with `attributes`. Naming the custom attributes namespace inside `attributes` is a `400`. | | Empty pieces | An empty object, an empty operator object, or an empty `$and`, `$or`, `$in` or `$nin` array is a `400`, not a filter that matches everything. | | Unknown operators | A `400`. There is no `$contains`, `$regex` or fuzzy operator. | diff --git a/essentials/v2/connectors.mdx b/essentials/v2/connectors.mdx index dd569854..03cfd492 100644 --- a/essentials/v2/connectors.mdx +++ b/essentials/v2/connectors.mdx @@ -109,7 +109,7 @@ Each resource accepts the following optional fields: | Field | Purpose | |---|---| | `collection` | Routes objects from this resource into a specific collection (overrides the connector-level `collection`) | -| `metadata` | Key-value pairs merged into the attributes of every synced object from this resource. Undeclared keys are accepted but only keys in `database_metadata_schema` are indexed for filtering. | +| `metadata` | Key-value pairs merged into the attributes of every synced object from this resource. Only keys in `database_metadata_schema` are filterable. | | `additional_metadata` | Key-value pairs merged into the custom attributes of every synced object from this resource | | `acl` | Restricts every object synced from this resource to the listed principals. Omitted means unrestricted. See [Access Control](/essentials/v2/access-control). | diff --git a/essentials/v2/graph-collections-byog.mdx b/essentials/v2/graph-collections-byog.mdx index b80f9d16..b6362014 100644 --- a/essentials/v2/graph-collections-byog.mdx +++ b/essentials/v2/graph-collections-byog.mdx @@ -259,7 +259,7 @@ the expression text as the key; **alias everything you plan to parse** | Cypher value | JSON | |---|---| | string / boolean / null | JSON string / boolean / null | -| integer | JSON number. Graph integers are 64-bit; values beyond 2⁵³ lose precision in languages that parse numbers as doubles; keep your own ids inside the safe range, or return them as strings | +| integer | JSON number (64-bit). Values beyond 2⁵³ lose precision in double-based parsers, so keep ids in range or return them as strings | | float | JSON number | | list / map | JSON array / object (rendered recursively) | | **node** | object with all node properties, plus `id` and `labels` | @@ -446,7 +446,7 @@ Errors use the standard envelope, with the code and message repeated under | Status | Meaning | |---|---| -| `400` | Invalid request (missing fields, bad collection name), unsupported construct, **Cypher errors** (the compiler's message is passed through so you can fix the query), or **query timeout** | +| `400` | Invalid request, unsupported construct, **Cypher error** (compiler message passed through) or **query timeout** | | `401` | Missing or invalid API key | | `403` | The API key's scope does not permit this operation | | `404` | Unknown database; create it with `POST /byog/databases` | diff --git a/essentials/v2/ingest.mdx b/essentials/v2/ingest.mdx index fc1344f5..8db5a632 100644 --- a/essentials/v2/ingest.mdx +++ b/essentials/v2/ingest.mdx @@ -151,18 +151,18 @@ Each entry is exactly one of `text` or `conversation`. | Field | Notes | | --- | --- | -| `context_id` | Your id for the context. Generated from its text and `title` when omitted, so two contexts with the same text and title and no id collide. Must not contain commas. | +| `context_id` | Your id for the context; no commas. Generated from text and `title` when omitted, so same-text, same-title contexts without an id collide. | | `title` | Optional readable name. Searchable with `titles` on [query](/essentials/v2/query). At most 1,024 bytes. | | `text` | Plain text. Shape A. See [Text context](#4-text-context). | | `conversation` | A list of `{ role, content }` turns; roles are `user`, `assistant` and `system`. Shape B. See [Conversation context](#5-conversation-context). | | `enrich` | Extract entities, relations and preferences from this context. Default: the request's `enrich`, else `true`. | | `upsert` | Replace an existing context with the same `context_id`. Default: the request's `upsert`, else `true`. | | `instructions` | Steer enrichment for this context. At most 4,000 characters. Default: the request's `instructions`. | -| `happened_at` | The date the context is about, `YYYY-MM-DD` only. A timestamp is a `400`. HydraDB records when it received the context separately and returns that as `received_at` on query chunks. | +| `happened_at` | The date the context is about, `YYYY-MM-DD` only; a timestamp is a `400`. Receipt time is returned separately as `received_at`. | | `attributes` | Declared, filterable fields from the database's `database_metadata_schema`. See [Attributes](/essentials/v2/attributes). | | `custom_attributes` | Free-form fields. Not filterable with `attributes`. | -| `forceful_relations` | Relations you declare to other contexts: `{ "context_ids": ["chat-w1"], "properties": {} }`, where `context_ids` are the `context_id`s of the related contexts. See [Declared relations](#10-declared-relations). | -| `acl` | Principals allowed to retrieve the context, such as `user_email:a@x.com` or `domain:acme.com`. Omit for unrestricted, `[]` for nobody. A malformed principal is a `400`. See [Restricting a context](#9-restricting-a-context). | +| `forceful_relations` | Relations to other contexts: `{ "context_ids": ["chat-w1"], "properties": {} }`. See [Declared relations](#10-declared-relations). | +| `acl` | Who can retrieve the context, such as `domain:acme.com`. Omit for unrestricted, `[]` for nobody. See [Restricting a context](#9-restricting-a-context). | | `user_name` | The speaker for the context: the author of a text context, or the person in a conversation's `user` turns. Default `"User"`. | ### Limits and unrecognised fields diff --git a/essentials/v2/query.mdx b/essentials/v2/query.mdx index f9c14e46..22cadfdd 100644 --- a/essentials/v2/query.mdx +++ b/essentials/v2/query.mdx @@ -172,11 +172,11 @@ See [When to use each](/essentials/v2/databases-and-collections#2-when-to-use-ea | --- | --- | --- | | `database` | string | Required. The database to search. | | `collection` | string | Single-scope selector. Send one collection to search only it; the default collection when neither this nor `collections` is sent. | -| `collections` | `string[]` or `{ [collection]: positive number (max one decimal place) }` | Preferred selector. A list fans out with equal normalized weights; an object applies relative ranking weights. Maximum 100 collections. `max_results` caps the merged result. | +| `collections` | `string[]` or `{ [collection]: positive number (max one decimal place) }` | Preferred selector. A list weights collections equally; an object sets relative weights. Max 100; `max_results` caps the merged result. | | `ids` | `string[]` | Restrict retrieval to these `context_id`s, at most 200. | -| `titles` | `string[]` | Restrict retrieval to context with one of these exact titles (case-insensitive, ORed), at most 500. Intersected with `ids` when both are sent. | -| `acl` | `string[]` | Query on behalf of an identity: results are restricted to context that identity may retrieve. Omitted, empty or `["*"]` disables filtering. See [Access control](/essentials/v2/access-control). | -| `attributes` | object | Filter on declared attributes with operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$exists`. Applies to chunks, forceful relations and graph paths alike. See [Attributes](/essentials/v2/attributes). | +| `titles` | `string[]` | Exact titles to match (case-insensitive, ORed), at most 500. Intersected with `ids` when both are sent. | +| `acl` | `string[]` | Return only what this identity may retrieve. Omitted, empty or `["*"]` disables it. See [Access control](/essentials/v2/access-control). | +| `attributes` | object | Filter on declared attributes with operators such as `$eq`, `$in`, `$gte` and `$and`. See [Attributes](/essentials/v2/attributes). | ```json { @@ -199,24 +199,24 @@ See [When to use each](/essentials/v2/databases-and-collections#2-when-to-use-ea | `query` | string | Required. The question or search terms. | | `query_by` | `"hybrid"` or `"text"` | `hybrid` (default) blends semantic and BM25; `text` is BM25 only, paired with `operator`. | | `operator` | `"or"`, `"and"`, `"phrase"` | BM25 term matching for `query_by: "text"`. Default `"or"`. | -| `mode` | `"auto"`, `"fast"`, `"thinking"` | `auto` (default) scores the query and routes to `fast` or `thinking`. `thinking` expands the query, reranks and follows declared relations; `fast` is one pass. | +| `mode` | `"auto"`, `"fast"`, `"thinking"` | `auto` (default) routes to `fast` (one pass) or `thinking` (expansion, reranking, declared relations). | | `alpha` | float `0.0` to `1.0`, or `"auto"` | Semantic versus keyword blend for `hybrid`. Default `0.8`; `"auto"` also resolves to `0.8`. | | `max_results` | integer | Maximum chunks to return. Default `10`, maximum `250`. | | `recency_bias` | float `0.0` to `1.0` | Boost for newer content. Default `0.4`; send `0` to disable recency entirely. | -| `query_apps` | boolean | Default `true`. Adds app-aware retrieval (exact IDs, actors, thread and parent traversal) for connector content, on top of normal retrieval. Set `false` to skip it. | +| `query_apps` | boolean | Default `true`. Adds app-aware retrieval (exact IDs, actors, threads) for connector content. | ### Graph and relations | Parameter | Type / values | Purpose | | --- | --- | --- | -| `graph_context` | boolean | Default `true`. Include graph paths in `graph[]`. Set `false` for chunks only; `graph` is then `[]`. | -| `follow_forceful_relations` | boolean | Default `true`. Pull in the context each hit declared with `forceful_relations` at ingest, into `forceful_relations[]`. Declared relations are followed only in `thinking` mode. Set `false` for `forceful_relations: []`. `query_forceful_relations` is the deprecated alias. | +| `graph_context` | boolean | Default `true`. Include graph paths in `graph[]`; `false` returns `graph: []`. | +| `follow_forceful_relations` | boolean | Default `true`. Add the context hits declared with `forceful_relations` at ingest, in `thinking` mode only. Alias: `query_forceful_relations`. | ### Time | Parameter | Type / values | Purpose | | --- | --- | --- | -| `temporal_reasoning` | boolean | Default `true`. Resolve time-based questions (current, as of, ranges, upcoming). Matched facts come back in `chunks[].temporal`. Never changes which chunks are returned. | +| `temporal_reasoning` | boolean | Default `true`. Resolves time-based questions into `chunks[].temporal`. Never changes which chunks are returned. | | `temporal_now` | ISO 8601 string | The time to treat as now. Set it when replaying past conversations. | --- @@ -236,11 +236,11 @@ The matched pieces of your context, ranked. Preserve the order. | `chunk_id` | string | The chunk's id. Referenced from `graph[].triplets[].relation.chunk_id`. | | `context_id` | string | The context this chunk came from. Pass it to `GET /context/inspect`. | | `score` | number | Relevance. Always present. | -| `content` | string | The chunk's own text, verbatim. Enrichment is not concatenated into it. | -| `enrichment` | string | What enrichment extracted from this chunk: the extracted statement (a preference, a fact). Omitted when enrichment extracted nothing. | +| `content` | string | The chunk's own text, verbatim. | +| `enrichment` | string | The statement enrichment extracted from this chunk. Omitted when there is none. | | `enrichment_kind` | string | An optional label; omitted when none was set. | -| `received_at` | string | When HydraDB received the context this chunk came from, as an RFC 3339 timestamp (for example `2026-07-02T09:14:05Z`). This is the ingest time, not its `happened_at`, which is not returned here. Omitted when no receipt time is recorded for the chunk, as on context ingested before it existed; it is never sent empty. | -| `temporal` | array | Present only when the query engaged temporal reasoning. Each entry is `{ content, start_date, end_date }`: `content` reads `. Start: YYYY-MM-DD, End: YYYY-MM-DD` (only the dated sides are printed), and either date may be `null`. | +| `received_at` | string | When HydraDB received the source context (ingest time), RFC 3339. Omitted when none is recorded. | +| `temporal` | array | Only when temporal reasoning engaged. Entries are `{ content, start_date, end_date }`; either date may be `null`. | **Chunks carry almost nothing about their source.** A chunk has no title, url, collection or attributes; the one source detail it carries is `received_at`. `llm_prompt` prints the title, collection, type, last-updated date and url for the model. To show a context's title, timestamp or attributes yourself, call [`POST /context/list`](/api-reference/v2/endpoint/list-documents) with `ids: [""]`; [`GET /context/inspect`](/api-reference/v2/endpoint/fetch-content) returns its stored content. @@ -259,10 +259,10 @@ One flat array of paths through the [context graph](/essentials/v2/context-graph | `triplets[].relation.predicate` | string | The relation, for example `subscribed to`. | | `triplets[].relation.context` | string | The sentence the relation was extracted from. | | `triplets[].relation.temporal_details` | string | When the relation holds, for example `since June`. Omitted when empty. | -| `triplets[].relation.timestamp` | number | When the relation was introduced (the date of the source it was extracted from), in Unix epoch seconds, possibly fractional (for example `1782984600`). Omitted when the edge has none. | +| `triplets[].relation.timestamp` | number | When the relation was introduced, in Unix epoch seconds (may be fractional). Omitted when the edge has none. | | `triplets[].relation.relationship_id` | string | The relation's id. | | `triplets[].relation.chunk_id` | string | The chunk this relation was extracted from. Use it to attach the hop to a chunk, below. | -| `path_summary` | string | One sentence summarizing the whole path. Never empty: when the server wrote no summary for a path, it narrates the hops, such as `Priya owns refund processing.` | +| `path_summary` | string | One sentence summarizing the path. Never empty; falls back to narrating the hops. | ### Attaching graph paths to chunks @@ -340,25 +340,25 @@ The sections, in order: | Section | Contents | | --- | --- | -| `# Query results` | `**Query:**` (the query); an `**Interpreted:**` line when the query was widened by an alias (a workspace nickname for a name) or a resolved reference; a `**Found:**` line counting what follows; a `**Note:**` line when a temporal, source or profile lookup was degraded or truncated, so a thin answer is not read as an absence; and, when there is a result, the line telling the model to cite it by its number. | -| `## Results` | One block per entry of `chunks[]`, in ranked order, separated by `---`: a `### 1. title` heading; a line with `**Relevance:**` (the `score`), `**Collection:**`, `**Type:**` and `**Category:**` (the `enrichment_kind`); a line with `**Id:**` (the `context_id`) and `**Last updated:**`; the chunk's `content`; then `**Enrichment:**` with the `enrichment`. | -| `## Forceful relations` | A guide line, then one `### R1. title` block per entry of `forceful_relations[]`, laid out like a result, with `**Linked from:**` (the `via.from` context, when it is not `""`) in place of `**Relevance:**`. | -| `## Related facts` | One line per path in `graph[]`, such as `- [P1] **A** -pred→ **B** (relevance 0.81) [1]`: the path's label, its chain of hops, the path's relevance after reranking in parentheses (printed only here: `graph[]` carries no score), and the results its hops were extracted from. A path with no reranked score, such as a graph summary a `thinking` query builds, has no parenthetical at all: `- [P3] **A** -pred→ **B** [1]`. The line never says how the path was found (`graph[].origin` does). The `path_summary` is indented on the line under it, unless it only narrates the hops the chain already shows. | -| `## Temporal facts` | For a "how long between" question, a `**Duration:**` line first: the computed days, whether they are approximate, and the two dated facts it was measured between. Then one line per dated fact the query engaged (the facts behind `chunks[].temporal`): subject, relation and object, then the resolved window, fact type, precision and status, with the evidence phrase set apart after a `;`, citing its result (or naming its source id when that fact's chunk is not a result). | -| `## Source facts` | App-native facts about the sources behind the results (who acted, in what role, where, in which thread, from which connector, when synced), citing their result. Prompt only: no JSON key carries them. | -| `## Profiles` | The entity profiles the query selected, one `### name` block each: headline, summary and the profile's statements. Prompt only. | -| `## Code search` | The repository code-search answer, one `### repository` block each, with its status. Prompt only. | -| `## Sources` | Each context once, in order of first appearance: title, type, id, url and last-updated date. Only web (`http` or `https`) links are printed; a storage location such as `s3://...` never is. | +| `# Query results` | The query, then `**Interpreted:**`, `**Found:**` and `**Note:**` lines when they apply, and the instruction to cite results by number. | +| `## Results` | One `### 1. title` block per `chunks[]` entry, in ranked order: relevance, collection, type, id, last updated, `content`, `**Enrichment:**`. | +| `## Forceful relations` | One `### R1. title` block per `forceful_relations[]` entry, with `**Linked from:**` in place of relevance. | +| `## Related facts` | One line per `graph[]` path, such as `- [P1] **A** -pred→ **B** (relevance 0.81) [1]`, with `path_summary` indented below. | +| `## Temporal facts` | A `**Duration:**` line for "how long between" questions, then one line per dated fact behind `chunks[].temporal`. | +| `## Source facts` | App-native facts (actor, role, place, thread, connector, sync time), citing their result. Prompt only. | +| `## Profiles` | The selected entity profiles, one `### name` block each. Prompt only. | +| `## Code search` | The code-search answer, one `### repository` block each, with its status. Prompt only. | +| `## Sources` | Each context once, in first-appearance order: title, type, id, url (web links only) and last-updated date. | `**Type:**` is what the context is: the connector's word for it (a Slack `message`, a Jira `ticket`) when a connector set one, otherwise its source type, such as `file`. A field with no value is left out of its line. | Label | Refers to | | --- | --- | -| `[1]`, `[2]`, ... | Result `### 1.`, `### 2.`, ...: that entry of `chunks[]`. Its `**Id:**` is the `context_id` to pass to `GET /context/inspect`. | +| `[1]`, `[2]`, ... | Result `### 1.`, `### 2.`, ...: that `chunks[]` entry. Its `**Id:**` is the `context_id`. | | `[R1]`, `[R2]`, ... | Forceful relation `### R1.`, `### R2.`, ...: that entry of `forceful_relations[]`. | -| `[P1]`, `[P2]`, ... | A related fact: path 1, 2, ... of `graph[]`, the same numbering the dashboard and CLI show next to each hop. An agent can cite a fact by its label. A path that carries a decision trace has an indented `**Decision:**` line under it with the decision, when it was made, who made it and the evidence. | +| `[P1]`, `[P2]`, ... | Path 1, 2, ... of `graph[]`, numbered as in the dashboard and CLI. | -A related fact or a temporal fact ends with the labels of the results it was extracted from (`[1]`, or `[R1]` for a forceful relation); a fact extracted from a chunk that is not in the response carries none. The numbers in `## Sources` count contexts, not results, and are not citation labels. +A related fact's `(relevance ...)` is left out when the path has no reranked score, and a path that carries a decision trace has an indented `**Decision:**` line under it. A related fact or a temporal fact ends with the labels of the results it was extracted from (`[1]`, or `[R1]` for a forceful relation); a fact extracted from a chunk that is not in the response carries none. The numbers in `## Sources` count contexts, not results, and are not citation labels. A related fact's chain reads left to right: diff --git a/essentials/v2/webhooks.mdx b/essentials/v2/webhooks.mdx index d1c4ae57..3e58f999 100644 --- a/essentials/v2/webhooks.mdx +++ b/essentials/v2/webhooks.mdx @@ -259,7 +259,7 @@ HydraDB sends a `POST` request with a JSON body. | `Content-Type` | Always `application/json` | | `X-HydraDB-Delivery-ID` | Stable delivery ID for this event | | `X-HydraDB-Event` | Event name, such as `indexing.status_changed` | -| `X-HydraDB-Signature` | `sha256=`, the HMAC-SHA256 of the raw request body keyed by your signing secret. Present only when signing is configured. See [Verifying signatures](#5-verifying-signatures) | +| `X-HydraDB-Signature` | `sha256=` HMAC-SHA256 of the raw body, keyed by your signing secret. Only when signing is configured. See [Verifying signatures](#5-verifying-signatures) | The signature scheme in full: @@ -314,7 +314,7 @@ For failed indexing, the payload can include `error_code` and `error_message`: | `event` | Event type. Currently `indexing.status_changed`. | | `delivery_id` | Stable ID for this event. Store it to deduplicate retries. | | `id` | The context's `context_id`: the one you supplied at ingestion, or the generated one. For connector-synced content, the connector context's id. | -| `database` | The name of the database you ingested into: the value you sent as `database` (or `tenant_id`) on the ingest request. Empty only for context ingested before this field existed. | +| `database` | The database you ingested into, as sent on the ingest request. Empty only for context ingested before this field existed. | | `collection` | Collection scope for the indexed context. | | `status` | Terminal indexing status. Usually `completed` or `errored`. | | `timestamp` | Time the webhook payload was created. | @@ -781,9 +781,9 @@ The overlap lives in your receiver, not in HydraDB. Each delivery carries a sing | Issue | What to check | |---|---| | Test delivery fails | Confirm your endpoint is public and returns a `2xx` status. | -| Signature check fails | Verify the HMAC is computed over the raw request body, not parsed JSON. Check you are comparing against the whole header value including the `sha256=` prefix, and that the digest is lowercase hex rather than base64. | +| Signature check fails | Compute the HMAC over the raw body, not parsed JSON, and compare lowercase hex against the whole header, including `sha256=`. | | Signature header is missing | Signing is not configured. Call `POST /webhooks/indexing/signing-secret` to enable it. | -| Signatures started failing after a rotation | Rotation applies immediately. Confirm your receiver has the new secret deployed, and see [Zero-downtime key rotation](#zero-downtime-key-rotation) to avoid the gap next time. | +| Signatures started failing after a rotation | Rotation is immediate; deploy the new secret. See [Zero-downtime key rotation](#zero-downtime-key-rotation). | | Event arrives more than once | This is expected during retries. Deduplicate with `delivery_id`. | | Event never arrives | Check the dashboard delivery history for `failed` or `permanently_failed`. | | `id` is unexpected | It is the context's `context_id` (the one you supplied, or the generated one), or the connector context's id for synced content. | diff --git a/plugins/claude-code.mdx b/plugins/claude-code.mdx index 5112ee34..b9c4138b 100644 --- a/plugins/claude-code.mdx +++ b/plugins/claude-code.mdx @@ -168,7 +168,7 @@ The earlier names `/hydradb:status`, `/hydradb:search`, `/hydradb:remember`, `/h | `captureMode` | `session-upsert` | How conversations are saved (see [Modes](#modes)) | | `recallMode` | `fast` | Recall depth: `fast` or `thinking`, sent as the query `mode` | | `graphContext` | `true` | Include related facts from the context graph in recalled context | -| `followForcefulRelations` | `true` | Follow the relations declared at ingest, so recall also returns the linked context under Forceful relations. The server follows them in `thinking` mode. Env var: `HYDRADB_FOLLOW_FORCEFUL_RELATIONS` | +| `followForcefulRelations` | `true` | Also recall context linked at ingest (followed in `thinking` mode). Env var: `HYDRADB_FOLLOW_FORCEFUL_RELATIONS` | ### Limits diff --git a/plugins/mcp.mdx b/plugins/mcp.mdx index 3f2d119c..7218a800 100644 --- a/plugins/mcp.mdx +++ b/plugins/mcp.mdx @@ -294,7 +294,7 @@ independent users. Send them as headers: | ------ | ------- | -------- | | `Authorization` | Your HydraDB API key as a `Bearer` token (`X-HydraDB-Api-Key` is also accepted), or an OAuth access token | Yes | | `X-HydraDB-Database` | Database (tenant scope). API-key requests only; ignored for an OAuth token, whose scope comes from what you approved | Yes\* | -| `X-HydraDB-Collection` | Collection (sub-tenant). Unset, a query searches every collection in the database (up to 10; past that, only the default collection) and a write goes to the default collection | No | +| `X-HydraDB-Collection` | Collection (sub-tenant). Unset: queries search all collections (up to 10, else the default) and writes go to the default | No | | `X-HydraDB-Graph-Database` | Default graph database for the Cypher tools; defaults to the request's database | No | | `X-HydraDB-Graph-Collection` | Default graph collection; defaults to `default` | No | @@ -442,7 +442,7 @@ same scope names the rest of the product uses. See the | Tool | What it does | | ---- | ------------ | -| `hydradb_query` | Query the database; returns the server-built `llm_prompt` (ranked results, forceful relations, related facts from the context graph) plus the same answer as structured content | +| `hydradb_query` | Query the database; returns the server-built `llm_prompt` plus the same answer as structured content | | `hydradb_ingest` | Store a note or document (`text`) or a conversation (`turns`) as one context; HydraDB enriches it and adds it to the context graph | | `hydradb_list` | List what is stored in a collection, one page at a time | | `hydradb_inspect` | Retrieve the original content of a stored context by ID | @@ -463,13 +463,13 @@ Sends the question to `POST /query` and returns the answer described under | `max_results` | number | No | Chunks to return, 1-50 (default: `10`) | | `mode` | string | No | `thinking` (default) expands the query, reranks and follows forceful relations; `fast` is one pass and quicker; `auto` lets HydraDB pick | | `graph_context` | boolean | No | Include related facts from the context graph (`graph[]`) in the answer (default: `true`) | -| `follow_forceful_relations` | boolean | No | Also return context declared related at ingest (see `forceful_relations` on `hydradb_ingest`), listed under Forceful relations with `[R1]` labels (default: `true`). They are followed in `thinking` mode | -| `operator` | string | No | `or`, `and`, or `phrase`. Switches the query to keyword retrieval, which matches the literal words instead of running hybrid semantic search. Leave unset for normal searches | +| `follow_forceful_relations` | boolean | No | Also return context linked at ingest via `forceful_relations`, labelled `[R1]` (default: `true`). Followed in `thinking` mode | +| `operator` | string | No | `or`, `and`, or `phrase`: use keyword retrieval on the literal words. Unset runs hybrid semantic search | | `source_ids` | string[] | No | Restrict the search to these context IDs (context IDs from earlier results or `hydradb_list`). No match returns an empty result | | `titles` | string[] | No | Restrict the search to context whose **complete** title exactly matches any value, ignoring case | | `recency_bias` | number | No | Favour recently updated context when ranking, 0-1 (default: `0`). Re-ranks only; it never excludes older context | | `query_apps` | boolean | No | App-aware retrieval over connector content: exact IDs and actors, thread reconstruction, parent and child expansion (default: `false`) | -| `acl` | string[] | No | Principals to answer as: an email, a `domain:`, or a `group::`. Results are limited to context whose access list admits one of them. Omit to search everything the key can reach. See [Access control](/essentials/v2/access-control) | +| `acl` | string[] | No | Principals to answer as (email, `domain:`, `group::`). Omit to search all. See [Access control](/essentials/v2/access-control) | | `collections` | string[] | No | Search several collections at once. Pass either this or `collection`, not both | | `database` | string | No | Database (tenant) scope override for this request | | `collection` | string | No | Collection (sub-tenant) scope override for this request | @@ -536,7 +536,7 @@ exactly one of `text` or `turns`. Passing both is rejected. | `happened_at` | string | No | Calendar date `YYYY-MM-DD` when the fact was true, as opposed to when it was stored | | `attributes` | object | No | Declared, filterable key/value attributes (keys from the database's metadata schema). See [Attributes](/essentials/v2/attributes) | | `custom_attributes` | object | No | Free-form key/value data stored with the context, not filterable | -| `forceful_relations` | string[] | No | Context IDs this context is declared related to, such as the thread or document it belongs to. A later query that returns this context can pull them in under Forceful relations | +| `forceful_relations` | string[] | No | Context IDs this context is related to, such as its thread or document. Queries returning it can pull them in | | `acl` | string[] | No | Principals that may read the context: an email, a `domain:`, or a `group::`. Omit it for a context anyone holding the key may read | | `database` | string | No | Database (tenant) scope override for this request | | `collection` | string | No | Collection (sub-tenant) scope override for this request | From 626b9d0c45936224cc33845f2508af059339c71c Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 09:59:40 +0530 Subject: [PATCH 13/17] docs: list all five reserved forceful_relations property keys Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/v2/endpoint/ingest-context.mdx | 2 +- api-reference/v2/openapi.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index 45ad427b..fabd7f19 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -136,7 +136,7 @@ Each entry in `context` is exactly one of `text` or `conversation`. | | Declared, filterable fields; keys must be in `database_metadata_schema`. Filter with `attributes` on `/query`. See [Attributes](/essentials/v2/attributes). | | | Free-form fields. Stored with the context; not filterable and not returned on query chunks. | | | `{ "context_ids": [...], "properties": {} }` links to other contexts, followed in `thinking` mode via `follow_forceful_relations`. Ids use `context_id` rules. | -| | Optional flat map of string, number or boolean values stored on each edge. At most 1 KiB; no empty key and no reserved key such as `id`. | +| | Optional flat map of string, number or boolean values stored on each edge. At most 1 KiB; no empty key, and not `id`, `created_at`, `relation_type`, `tenant_id` or `sub_tenant_id`. | | | Allowed principals: emails, `user_email:`, `group:`, `domain:`, or `__public__`. Omit for unrestricted, `[]` for nobody; malformed is a `400`. | | | The speaker for the context: the author of a `text` context, or the person in a conversation's `user` turns. (default=`"User"`) | diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index 982d5029..d0daf405 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -4892,7 +4892,7 @@ "type": "string" }, "collections": { - "description": "Collections to search: a list for equal weighting, or an object mapping collection ID to a positive ranking weight (one decimal place). Do not combine with `sub_tenant_ids`.", + "description": "Collections to search: a list for equal weighting, or an object mapping collection ID to a positive ranking weight (one decimal place). Do not combine with `sub_tenant_id` or `sub_tenant_ids`.", "example": [ "team_docs", "engineering" @@ -7681,7 +7681,7 @@ } }, { - "description": "Principals to answer as; the subgraph holds only contexts they may see, checked at every hop. Repeat the parameter or send a comma-separated list.", + "description": "Principals to answer as; only contexts they may see are returned, checked at every hop. Omit for no access scoping. Repeat or comma-separate.", "in": "query", "name": "acl", "schema": { From 78ab02595c7c5b916b65bbbd1810165aad2f6819 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 09:59:55 +0530 Subject: [PATCH 14/17] docs: keep the reserved-keys cell to two lines Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/v2/endpoint/ingest-context.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index fabd7f19..74f0ce6c 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -136,7 +136,7 @@ Each entry in `context` is exactly one of `text` or `conversation`. | | Declared, filterable fields; keys must be in `database_metadata_schema`. Filter with `attributes` on `/query`. See [Attributes](/essentials/v2/attributes). | | | Free-form fields. Stored with the context; not filterable and not returned on query chunks. | | | `{ "context_ids": [...], "properties": {} }` links to other contexts, followed in `thinking` mode via `follow_forceful_relations`. Ids use `context_id` rules. | -| | Optional flat map of string, number or boolean values stored on each edge. At most 1 KiB; no empty key, and not `id`, `created_at`, `relation_type`, `tenant_id` or `sub_tenant_id`. | +| | Flat map of scalars on each edge, at most 1 KiB. Reserved keys: `id`, `created_at`, `relation_type`, `tenant_id`, `sub_tenant_id`. | | | Allowed principals: emails, `user_email:`, `group:`, `domain:`, or `__public__`. Omit for unrestricted, `[]` for nobody; malformed is a `400`. | | | The speaker for the context: the author of a `text` context, or the person in a conversation's `user` turns. (default=`"User"`) | From 4b9ece2840d109a5822794061fb1ef99e775e072 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 10:00:29 +0530 Subject: [PATCH 15/17] docs: graph[] reads as paths inside the context graph Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/v2/endpoint/query-overview.mdx | 2 +- api-reference/v2/endpoint/query.mdx | 2 +- api-reference/v2/openapi.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index b4486255..4db8ac29 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -159,7 +159,7 @@ Use text query when literal wording matters: legal clauses, SKUs, error codes, I | Key | Contents | | --- | --- | | `chunks[]` | Ranked matches with `chunk_id`, `context_id`, `score`, `content`, and optional `enrichment`, `enrichment_kind`, `received_at` and `temporal`. | -| `graph[]` | Paths through the context graph: `origin`, `triplets[]` and `path_summary`. Each hop's `relation.chunk_id` names its chunk. | +| `graph[]` | Paths inside the context graph, each having `triplets[]` and a `path_summary`. Each hop's `relation.chunk_id` names its chunk. | | `forceful_relations[]` | Chunks linked at ingest with `forceful_relations`, each with its `via` link. `thinking` mode only. | | `llm_prompt` | Server-built markdown to inject into a model call. Cites results `[1]`, forceful relations `[R1]` and graph paths `[P1]`. | diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index 3ee6956d..c453e299 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -544,7 +544,7 @@ The generated response schema on this page is a union of two bodies: the older v | Key | Contents | | --- | --- | | `chunks[]` | Ranked matches with `chunk_id`, `context_id`, `score`, verbatim `content`, and optional `enrichment`, `enrichment_kind`, `received_at` and `temporal[]`. | -| `graph[]` | Paths through the context graph, each with `origin`, `triplets[]` and `path_summary`. `[]` when `graph_context` is `false`. | +| `graph[]` | Paths inside the context graph, each having `triplets[]` and a `path_summary`. `[]` when `graph_context` is `false`. | | `forceful_relations[]` | Chunks linked at ingest with `forceful_relations`, each with its `via` link. Followed only in `thinking` mode. | | `llm_prompt` | Server-built markdown to inject into a model call, citing `[1]`, `[R1]` and `[P1]`. See [Query](/essentials/v2/query#llm_prompt). | diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index d0daf405..6c54c069 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -5125,7 +5125,7 @@ "type": "array" }, "graph": { - "description": "Graph paths, query paths first then chunk relations, deduplicated. [] when graph_context was false.", + "description": "Paths inside the context graph, each having `triplets` and a `path_summary`. `[]` when `graph_context` is `false`.", "items": { "$ref": "#/components/schemas/search.QueryGraphPath" }, From b75a1b084d515fb24d8341037afdbcea041c7b05 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 10:10:56 +0530 Subject: [PATCH 16/17] docs: the attributes query filter is plain key-value pairs Owner decision: /query attributes is documented as key-value pairs matched exactly against declared attributes, one value per key, all must match. The operator language is no longer documented anywhere; the spec is the docs view at app PR #1672, whose lint now rejects operators. metadata_filters stays deprecated, and is still how custom_attributes are filtered. Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 39 +---- api-reference/v2/endpoint/query-overview.mdx | 11 +- api-reference/v2/endpoint/query.mdx | 26 +--- api-reference/v2/openapi.json | 6 +- api-reference/v2/sdks.mdx | 4 +- essentials/v2/architecture.mdx | 2 +- essentials/v2/attributes.mdx | 144 +++---------------- essentials/v2/query.mdx | 11 +- essentials/v2/semantic-search.mdx | 12 +- essentials/v2/split-databases.mdx | 2 +- get-started/v2/core-concepts.mdx | 2 +- 11 files changed, 54 insertions(+), 205 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index 9281b6e9..11ae146b 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -222,7 +222,7 @@ With `enrich: true` (the default) HydraDB reads each context and extracts entiti - Scope with `collection` (one) or `collections` (a list, or `{ "name": weight }` to rank one collection above another). - `query_by: "hybrid"` (default) blends semantic and BM25 retrieval; `query_by: "text"` is BM25 keyword or phrase search. - `mode: "auto"` (default) routes each query; `"fast"` is one low-latency pass; `"thinking"` expands the query, reranks, traverses the graph further and follows forceful relations. -- `attributes` filters on declared attributes with operators such as `$eq` and `$in`. +- `attributes` filters on declared attributes with key-value pairs, such as `{"department": "support"}`. The response `data` is always the same four keys: `chunks`, `graph`, `forceful_relations` and `llm_prompt`. See [Query API](#8-query-api). @@ -915,12 +915,7 @@ Attribute-filtered search, on behalf of one user: "database": "acme_corp", "collection": "company", "query": "What is the refund window for enterprise customers?", - "attributes": { - "$and": [ - { "department": { "$eq": "support" } }, - { "region": { "$in": ["us", "eu"] } } - ] - }, + "attributes": { "department": "support", "region": "us" }, "acl": ["user_email:grace@acme.com"] } ``` @@ -1239,34 +1234,14 @@ To change an indexed context's attributes, re-ingest it with the same `context_i ### Filtering with attributes -`attributes` on `POST /query` is an operator filter over declared attributes. It applies to chunks, forceful relations and graph paths alike. - -| Operator | Meaning | -|---|---| -| `$eq`, `$ne` | equal, not equal (a bare scalar value means `$eq`) | -| `$gt`, `$gte`, `$lt`, `$lte` | numeric or ordered comparison | -| `$in`, `$nin` | value is, or is not, in an array | -| `$exists` | the field is, or is not, set (`true` / `false`) | -| `$and`, `$or` | an array of sub-filters | -| `$not` | a sub-filter to negate | - -```json -{ - "attributes": { - "$or": [ - { "department": "support" }, - { "$and": [ { "priority": { "$gte": 7 } }, { "region": { "$in": ["us", "eu"] } } ] } - ] - } -} -``` +`attributes` on `POST /query` is a set of key-value pairs, such as `{"department": "support", "priority": 3}`. It applies to chunks, forceful relations and graph paths alike. Rules: -- There is no `$contains` and no fuzzy match. Put fuzzy concepts in `query`, or declare a `VARCHAR` field with `enable_dense_embedding` and include the concept in the query. -- `custom_attributes` cannot be filtered. Declare the field and send it in `attributes` instead. -- A field the database's schema does not declare is a `400`, as is an empty object (`{}`) anywhere in the filter. -- Each list holds at most 500 values, the whole filter is capped at 64 KiB, and nesting is capped at 10 levels. +- Each key is a field declared in the database schema, and each value must match that field's type. An unknown field or a mistyped value is a `400`. +- Keys are ANDed, with one value per key. +- There is no fuzzy match. Put fuzzy concepts in `query`, or declare a `VARCHAR` field with `enable_dense_embedding` and include the concept in the query. +- `custom_attributes` cannot be filtered with `attributes`. The deprecated `metadata_filters` still filters them, nested under `additional_metadata`. - Filters are hard constraints, not hints: a valid filter that matches nothing returns an empty result rather than widening the search. - Plan hot filter fields before the first ingest, and keep attribute names stable. diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index 4db8ac29..a90d9856 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -39,7 +39,7 @@ linkStyle default stroke:#64748b,stroke-width:2px; | | `"fast"`, `"thinking"`, `"auto"` | `"fast"` for low latency, `"thinking"` for reranking and declared relations, `"auto"` (default) to route between them. | | | integer | Control prompt size. Default `10`. | | | `0.0` to `1.0` or `"auto"` | Lower favors BM25 keywords, higher favors semantic similarity. Default `0.8`. | -| | object | Filter with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on fields declared in `database_metadata_schema`. | +| | object | Filter with key-value pairs, such as `{"department": "legal"}`, on fields declared in `database_metadata_schema`. | | | boolean | Include graph paths in `graph[]`. Default `true`. | | | boolean | Add context linked with `forceful_relations` at ingest. Default `true`; `thinking` mode only. | | | boolean | Add app-aware retrieval for connector content. Default `true`. | @@ -118,19 +118,14 @@ Use this when the same question should search several collection scopes and retu -Use `attributes` when you already know the slice you want. Keys are the fields declared in `database_metadata_schema` and sent as `attributes` at ingest; clauses combine with `$and` and `$or`. +Use `attributes` when you already know the slice you want. Keys are the fields declared in `database_metadata_schema` and sent as `attributes` at ingest. Keys are ANDed, with one value per key. ```json { "database": "acme", "query": "What launch constraints apply to enterprise customers?", "query_by": "hybrid", - "attributes": { - "$and": [ - { "department": { "$eq": "product" } }, - { "region": { "$in": ["us", "eu"] } } - ] - } + "attributes": { "department": "product", "region": "us" } } ``` diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index c453e299..86ae8ddf 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -40,7 +40,7 @@ result = client.query( follow_forceful_relations=True, # Hard filter on declared attributes. - attributes={"department": {"$eq": "support"}}, + attributes={"department": "support"}, ) print(result.data.llm_prompt) @@ -67,7 +67,7 @@ const result = await client.query({ followForcefulRelations: true, // Hard filter on declared attributes. - attributes: { department: { $eq: "support" } }, + attributes: { department: "support" }, }); console.log(result.data?.llmPrompt); @@ -88,7 +88,7 @@ curl -X POST 'https://api.hydradb.com/query' \ "recency_bias": 0.2, "graph_context": true, "follow_forceful_relations": true, - "attributes": { "department": { "$eq": "support" } } + "attributes": { "department": "support" } }' ``` @@ -350,7 +350,7 @@ The generated reference below lists every request field. What to know when you l | `titles` | whole scope | 500 exact titles, case-insensitive | | `graph_context`, `follow_forceful_relations`, `temporal_reasoning`, `query_apps` | `true` | | -`attributes` is the filter to use; `metadata_filters` is deprecated and ANDed with it when both are sent (use it only for connector fields under `additional_metadata`, see [Connectors](/essentials/v2/connectors)). `acl` answers as the given principals; omit it, or send `[]` or `["*"]`, for no access scoping (see [Access control](/essentials/v2/access-control)). +`attributes` is the filter to use; `metadata_filters` is deprecated and ANDed with it when both are sent; it is still how you filter `custom_attributes`, nested under `additional_metadata` (see [Connectors](/essentials/v2/connectors)). `acl` answers as the given principals; omit it, or send `[]` or `["*"]`, for no access scoping (see [Access control](/essentials/v2/access-control)). **Tuning heuristics.** @@ -384,27 +384,15 @@ The generated reference below lists every request field. What to know when you l - `attributes` is a hard constraint applied during retrieval, on the fields declared in the database's `database_metadata_schema` and sent as `attributes` at ingest. It uses operators: + `attributes` is a hard constraint applied during retrieval, on the fields declared in the database's `database_metadata_schema` and sent as `attributes` at ingest. It is a set of key-value pairs: ```json { - "attributes": { - "$and": [ - { "department": { "$eq": "support" } }, - { "region": { "$in": ["us", "eu"] } }, - { "priority": { "$gte": 3 } } - ] - } + "attributes": { "department": "support", "region": "us", "priority": 3 } } ``` - | Operator | Meaning | - | --- | --- | - | `$eq`, `$ne` | equal, not equal | - | `$gt`, `$gte`, `$lt`, `$lte` | comparisons | - | `$in`, `$nin` | value is (not) one of a list | - | `$exists` | the field is present | - | `$and`, `$or`, `$not` | combine or negate clauses | + Each value must match its field's type, and an unknown field or a mistyped value is a `400`. Keys are ANDed, with one value per key. Filters apply to chunks, forceful relations and graph paths alike; a valid filter that matches nothing returns an empty result, never a widened search. `custom_attributes` are not filterable. See [Attributes](/essentials/v2/attributes). diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index 6c54c069..21b925fb 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -4878,7 +4878,11 @@ }, "attributes": { "additionalProperties": {}, - "description": "Operator filter on declared attributes: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$exists`. Cannot filter `custom_attributes`.", + "description": "Key-value pairs matched exactly against the database's declared attributes, one value per key, all must match. For `custom_attributes`, use `metadata_filters`.", + "example": { + "department": "legal", + "priority": 3 + }, "type": "object" }, "code_search": { diff --git a/api-reference/v2/sdks.mdx b/api-reference/v2/sdks.mdx index 2ef82521..0625d031 100644 --- a/api-reference/v2/sdks.mdx +++ b/api-reference/v2/sdks.mdx @@ -357,7 +357,7 @@ filtered = client.query( database="my_first_database", collection="support", query="refund window", - attributes={"department": {"$eq": "support"}}, + attributes={"department": "support"}, ) ``` ```typescript TypeScript SDK @@ -382,7 +382,7 @@ const filtered = await client.query({ database: "my_first_database", collection: "support", query: "refund window", - attributes: { department: { $eq: "support" } }, + attributes: { department: "support" }, }); ``` diff --git a/essentials/v2/architecture.mdx b/essentials/v2/architecture.mdx index cc2cc885..b97e9ff4 100644 --- a/essentials/v2/architecture.mdx +++ b/essentials/v2/architecture.mdx @@ -177,7 +177,7 @@ A short cheat sheet for the parameters you'll touch most often, and where each o | `collections` | Query | Preferred query-time collection selector. Use a one-element list, a multi-scope list with equal weights, or a weighted object for fanout ranking. | | `attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Declared, filterable fields on a context. Must match the [database metadata schema](/essentials/v2/attributes) declared at database creation. | | `custom_attributes` | [Ingest](/api-reference/v2/endpoint/ingest-context) | Free-form fields per context. Stored with the context; not filterable with `attributes`. | -| `attributes` | [Query](/api-reference/v2/endpoint/query) | Deterministic narrowing with operators (`$eq`, `$in`, `$gte`, `$and`, ...) on declared attributes. | +| `attributes` | [Query](/api-reference/v2/endpoint/query) | Deterministic narrowing with key-value pairs on declared attributes, ANDed. | | `alpha` | [Query](/api-reference/v2/endpoint/query) | Blends semantic vs. keyword (BM25) scores in `query_by: "hybrid"`. | | `graph_context` | [Query](/api-reference/v2/endpoint/query) | On by default: returns relation paths from the [context graph](/essentials/v2/context-graphs) in `graph[]`. | | `mode` | [Query](/api-reference/v2/endpoint/query) | `"fast"` for low-latency single-pass retrieval; `"thinking"` for multi-query expansion and reranking. | diff --git a/essentials/v2/attributes.mdx b/essentials/v2/attributes.mdx index b47527b0..bd33a090 100644 --- a/essentials/v2/attributes.mdx +++ b/essentials/v2/attributes.mdx @@ -1,9 +1,9 @@ --- title: "Attributes" -description: "Declare filterable attributes in the database schema, attach attributes and custom attributes to context at ingest, and filter queries with the attributes operator language." +description: "Declare filterable attributes in the database schema, attach attributes and custom attributes to context at ingest, and filter queries with attributes." --- -Attributes are structured values you attach to each context you ingest. Use them when you already know a hard constraint before retrieval runs, such as `department=legal`, `region=us`, `status=published` or `priority >= 5`. +Attributes are structured values you attach to each context you ingest. Use them when you already know a hard constraint before retrieval runs, such as `department=legal`, `region=us` or `status=published`. A context carries two kinds: @@ -32,7 +32,7 @@ A query then filters on the declared fields: "query": "How do access control reviews work?", "attributes": { "department": "security", - "priority": { "$gte": 5 } + "priority": 7 } } ``` @@ -44,7 +44,7 @@ A query then filters on the declared fields: | I want to... | Put it in... | Then... | | --- | --- | --- | | Scope most queries by a field like department, region, plan, customer or status | `attributes` | Declare the field in `database_metadata_schema` and filter with `"attributes": { "department": "legal" }`. | -| Filter by a number or a date range | `attributes` | Store a number, or a date string in one fixed format such as `YYYY-MM-DD`, and filter with `$gt`, `$gte`, `$lt`, `$lte`. | +| Filter by a number or a flag | `attributes` | Declare an integer or `BOOL` field and filter on one exact value, such as `{"priority": 7}`. | | Keep source details like author, a Slack timestamp, an external ID or a document version | `custom_attributes` | No schema needed. Stored with the context, not filterable with `attributes`. | | Combine a hard scope with semantic search | `attributes` | Send the filter plus your natural-language `query`. The filter narrows the candidates; ranking still uses the query. | | Search semantically over a text attribute | `attributes`, on a `VARCHAR` field with `enable_dense_embedding: true` | Put the concept in `query`. Do not put fuzzy concepts in the filter. | @@ -132,7 +132,7 @@ await client.databases.create({ | Field | Type / values | Purpose | | --- | --- | --- | | `name` | string | The attribute key: a letter, then letters, numbers or underscores, at most 255 characters. Reserved system names such as `source_id` are rejected. | -| `data_type` | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the aliases `string`, `boolean`, `integer`, `float`, `object` | Default `VARCHAR`. `array` is rejected with `400`; see [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | +| `data_type` | `VARCHAR`, `BOOL`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `JSON`, or the aliases `string`, `boolean`, `integer`, `float`, `object` | Default `VARCHAR`. `array` is rejected with `400`. | | `max_length` | integer | Maximum length of one `VARCHAR` value. Default `1024`, maximum `65535`; cannot be raised later. | | `enable_match` | boolean | Turns on keyword matching (a text analyzer) for the field. An `attributes` filter works on every declared field whether or not this is set. | | `enable_dense_embedding` | boolean | Adds dense semantic search over a `VARCHAR` field. | @@ -171,7 +171,7 @@ The update is additive only: `GET /databases/{database}/metadata-schema` returns the current `fields` in the same shape `add_fields` accepts. - Context ingested before a field existed has no value for it, so a comparison on the new field does not match it. Re-ingest that context with the value to include it. + Context ingested before a field existed has no value for it, so a filter on the new field does not match it. Re-ingest that context with the value to include it. --- @@ -299,7 +299,7 @@ Exceeding either cap fails the whole request with `400 INVALID_INPUT` before any ## 4. Filter a query with `attributes` -`attributes` on [`POST /query`](/essentials/v2/query#2-request) is an operator object over the declared fields. Several fields in one object must all match. +`attributes` on [`POST /query`](/essentials/v2/query#2-request) is a set of key-value pairs. Each key is a field declared in `database_metadata_schema`, each value must match that field's type, and a context matches only when every pair matches, with one value per key. ```bash cURL @@ -313,8 +313,7 @@ curl -X POST 'https://api.hydradb.com/query' \ "query": "How do access control reviews work?", "attributes": { "department": "security", - "region": { "$in": ["us", "eu"] }, - "priority": { "$gte": 5 } + "priority": 7 } }' ``` @@ -323,11 +322,7 @@ result = client.query( database="acme_corp", collection="company", query="How do access control reviews work?", - attributes={ - "department": "security", - "region": {"$in": ["us", "eu"]}, - "priority": {"$gte": 5}, - }, + attributes={"department": "security", "priority": 7}, ) ``` ```typescript TypeScript SDK @@ -335,125 +330,24 @@ const result = await client.query({ database: "acme_corp", collection: "company", query: "How do access control reviews work?", - attributes: { - department: "security", - region: { $in: ["us", "eu"] }, - priority: { $gte: 5 }, - }, + attributes: { department: "security", priority: 7 }, }); ``` -Query chunks do not carry attributes. The filter decides which context can appear; see [Query](/essentials/v2/query) for what the response contains. - -### Operators - -| Operator | Operand | Matches context whose value... | -| --- | --- | --- | -| a bare value | a value of the field's type | equals it. `{"department": "legal"}` is the same as `{"department": {"$eq": "legal"}}`. | -| `$eq` | a value of the field's type | equals it. | -| `$ne` | a value of the field's type | is present and differs from it. | -| `$gt`, `$gte`, `$lt`, `$lte` | a value of the field's type | is greater than, at least, less than, or at most the operand. Numbers compare numerically; `VARCHAR` values compare as strings, character by character. | -| `$in` | a non-empty array, at most 500 values | equals any one of the listed values. | -| `$nin` | a non-empty array, at most 500 values | is present and equals none of the listed values. | -| `$exists` | `true` or `false` | has a value (`true`), or has none (`false`). | -| `$and` | a non-empty array of filter objects | matches every one of them. | -| `$or` | a non-empty array of filter objects | matches at least one of them. | -| `$not` | one filter object | does not match it, and has a value for every field it names. | - -Operators combine and nest: - -```json -{ - "attributes": { - "$or": [ - { "department": "legal" }, - { - "$and": [ - { "department": "security" }, - { "priority": { "$gte": 8 } } - ] - } - ], - "$not": { "region": "cn" }, - "priority": { "$gte": 3, "$lte": 9 } - } -} -``` - -### How the filter behaves - -| Behavior | Contract | -| --- | --- | -| Several fields in one object | AND. Every clause must match. | -| Several operators on one field | AND. `{"priority": {"$gte": 3, "$lte": 7}}` is a range. | -| Equality | Exact, against the **whole** stored value. Strings are case-sensitive. | -| Value types | Each operand must match the field's declared type. `{"priority": "7"}` on an `INT64` field is a `400`, not an empty result. | -| `JSON` fields | Only `$exists` applies. Any other operator on a `JSON` field is a `400`. | -| Missing values | A context with no value for a field never matches a comparison on it, including `$ne`, `$nin` and `$not`. Add `{"$exists": false}` in an `$or` to keep it. | -| Field names | Must be declared in `database_metadata_schema`; an undeclared or reserved field is a `400`. Without any schema, every field compares as a string. | -| Custom attributes | Cannot be filtered with `attributes`. Naming the custom attributes namespace inside `attributes` is a `400`. | -| Empty pieces | An empty object, an empty operator object, or an empty `$and`, `$or`, `$in` or `$nin` array is a `400`, not a filter that matches everything. | -| Unknown operators | A `400`. There is no `$contains`, `$regex` or fuzzy operator. | -| Nesting | At most 10 levels deep through `$and`, `$or` and `$not`. | -| Graph and forceful relations | The filter applies to chunks, forceful relations and graph paths alike. A graph path that touches a context the filter excludes is removed. | -| No matches | A valid filter that matches nothing returns an empty result. HydraDB never drops or widens the filter to find something. | -| Size | At most 500 values in each `$in` or `$nin` list, and 64 KiB for the whole object. See [Filter size limits](#filter-size-limits). | +The filter applies to chunks, forceful relations and graph paths alike. A filter that matches nothing returns an empty result; HydraDB never drops or widens it. `attributes` are hard constraints, not semantic hints. `{"mood": "happy"}` requires that exact stored value; it does not expand to "joyful" or "cheerful". To search attribute text semantically, declare a `VARCHAR` field with `enable_dense_embedding` and put the concept in the main `query`. -### No containment on multi-valued fields - -`attributes` compares a context's single stored value. There is no containment operator, so a multi-valued field cannot be matched by "does this context's list include X": - -- A declared field cannot be an array: `data_type: "array"` is rejected with `400`. -- A list sent as the value of a `VARCHAR` attribute is rejected at ingest. -- A `JSON` attribute cannot be compared at all (only `$exists` applies). -- A string that joins several values, such as `"alpha,beta"`, is one value: `$eq` and `$in` match it only as the whole string. - -`$in` runs the other way round: it asks whether the context's one value is among the values you list. - -If you need to select context by one member of a set: - -- Give each member you filter on its own `BOOL` attribute, such as `"tag_billing": true`, and filter with `{"tag_billing": true}`. This counts against the 32-field limit, so it suits a small, known set. -- If the set is really "who may see this context", use `acl` instead. See [Access control](/essentials/v2/access-control). - -### Filter size limits - -The [size limits](#size-limits) above bound the values you **store**. `attributes` on `/query` has its own, separate pair, which bound what you **send** at query time: - -| Limit | Cap | -| --- | --- | -| Values in one `$in` or `$nin` list | **500** | -| The whole `attributes` object | **64 KiB** (65,536 bytes) | - -The object total is measured on its compact JSON encoding in UTF-8 bytes, with field names, operator names and punctuation all counted. The per-list cap catches one runaway list; the object cap catches many individually legal lists adding up. - -Over either limit returns `400` before the query runs: - -``` -$in for "customer_id" must contain at most 500 values (got 743) - -attributes is too large (130251 bytes when serialized; the maximum is 65536). Reduce the number or size of filter values. -``` - - - Needing far more than 500 values in one filter usually means the constraint belongs in the data rather than the query. Add an attribute that groups those values (a segment, tier or cohort key) and filter on that instead. - - ### Errors -Every malformed filter is a `400` with code `VALIDATION_ERROR` and a message naming the problem, for example: - -``` -unknown attribute "regoin" -value for "priority" does not match its type INT64 -$in for "region" expects an array -unsupported operator "$contains" for attribute "tags" -attributes filter nests deeper than 10 levels -``` +| You send | Result | +| --- | --- | +| A key not declared in `database_metadata_schema` | `400`, such as `unknown attribute "regoin"`. | +| A value that does not match the field's type, such as `"7"` for an `INT64` field | `400`, such as `value for "priority" does not match its type INT64`. | +| A `custom_attributes` key, nested under `additional_metadata` | `400` telling you to use `metadata_filters`, the deprecated filter that still covers `custom_attributes`. | --- @@ -504,9 +398,7 @@ To page through context rather than run retrieval, use [`POST /context/list`](/a | Ingest returns `400` naming an undeclared field | An `attributes` key is not in the schema | Declare the field, or move it to `custom_attributes` if you never filter on it. | | A filter on a custom attribute is rejected | `attributes` cannot filter on `custom_attributes` | Declare the field, send it in `attributes`, and re-ingest. | | `400 value for "priority" does not match its type` | The operand's JSON type differs from the declared type, such as `"7"` for an `INT64` field | Send the declared type: `{"priority": 7}`. | -| `$in` does not find a context whose field holds several values | There is no containment | See [No containment on multi-valued fields](#no-containment-on-multi-valued-fields). | -| `$ne` or `$not` drops context that has no value for the field | Missing values never match a comparison | Add `{"field": {"$exists": false}}` under `$or`. | -| Query returns 0 results after adding a filter | Over-scoping: the combined constraints exclude everything, or the context predates the field | Start with one constraint, add the others one at a time, and check with `$exists`. | +| Query returns 0 results after adding a filter | The pairs together exclude everything, or the context predates the field | Start with one pair and add the others one at a time. Re-ingest context that predates the field. | | A value edited with `PATCH /context/{id}/metadata` still filters as the old value | The filter runs against the values indexed at ingest | Re-ingest the context with `upsert: true` and the same `context_id`. | | A schema field cannot be changed | Declared fields are immutable | Add a new field, or create a new database with the corrected schema and re-ingest. | | Adding a field with `enable_dense_embedding` or `enable_sparse_embedding` returns `400` | Embedding flags can only be set at database creation | Create a new database with the final schema and re-ingest. | @@ -524,7 +416,7 @@ To page through context rather than run retrieval, use [`POST /context/list`](/a **Multi-language corpora.** Declare a `language` field and route each query to the right language by passing `"attributes": { "language": "" }`. -**Date windows.** Declare a `published_on` field as `VARCHAR`, store every date in one fixed format such as `YYYY-MM-DD` so string order is date order, and filter with `{"published_on": {"$gte": "2026-01-01", "$lt": "2026-07-01"}}`. +**Tags.** A field holds one value per context, so to select context by one member of a small, known set, give each member its own `BOOL` field, such as `"tag_billing": true`, and filter with `{"tag_billing": true}`. Each counts against the 32-field limit. If the set is really "who may see this context", use [`acl`](/essentials/v2/access-control) instead. **Schema as a contract.** Treat `database_metadata_schema` as part of your data contract and review it like a database migration. Getting it wrong costs a re-ingest, because declared fields are immutable; getting it right costs one extra review. diff --git a/essentials/v2/query.mdx b/essentials/v2/query.mdx index 22cadfdd..1bde9853 100644 --- a/essentials/v2/query.mdx +++ b/essentials/v2/query.mdx @@ -176,19 +176,14 @@ See [When to use each](/essentials/v2/databases-and-collections#2-when-to-use-ea | `ids` | `string[]` | Restrict retrieval to these `context_id`s, at most 200. | | `titles` | `string[]` | Exact titles to match (case-insensitive, ORed), at most 500. Intersected with `ids` when both are sent. | | `acl` | `string[]` | Return only what this identity may retrieve. Omitted, empty or `["*"]` disables it. See [Access control](/essentials/v2/access-control). | -| `attributes` | object | Filter on declared attributes with operators such as `$eq`, `$in`, `$gte` and `$and`. See [Attributes](/essentials/v2/attributes). | +| `attributes` | object | Filter on declared attributes with key-value pairs, ANDed. See [Attributes](/essentials/v2/attributes). | ```json { "database": "acme", "collection": "company", "query": "What is the refund window for enterprise customers?", - "attributes": { - "$and": [ - { "department": { "$eq": "support" } }, - { "region": { "$in": ["us", "eu"] } } - ] - } + "attributes": { "department": "support", "region": "us" } } ``` @@ -466,7 +461,7 @@ Most of the time the defaults are right. When they are not, here is where to sta | `graph` is `[]` | `graph_context: false`, or nothing connects the results | Leave `graph_context` on; `mode: "thinking"` explores more of the graph. An empty array is normal when there is nothing to return. | | `forceful_relations` is `[]` | Nothing in the hits declared `forceful_relations`, `follow_forceful_relations: false`, or the query ran in `fast` mode | Declare relations at ingest, leave the flag on, and use `mode: "thinking"`. | | Recent context does not appear | Indexing not finished | Poll `GET /context/status?ids=...&database=...`; chunks are invisible until processing reaches at least `graph_creation`. | -| `attributes` does not narrow results | The key is not declared in `database_metadata_schema`, or the value does not match | Declare the field and send it in `attributes` at ingest; filter with an operator such as `$eq`. `attributes` cannot filter on `custom_attributes`. | +| `attributes` returns nothing | No context holds that exact value, or the context was ingested without it | Send the value in `attributes` at ingest and re-ingest older context. `attributes` cannot filter on `custom_attributes`. | | Chunk has no title or url | Chunks carry no title, url, collection or attributes by design | Read them from `llm_prompt`, or call `POST /context/list` with `ids: [""]`. | | `operator: "phrase"` ignored | `query_by` is not `"text"` | `operator` only applies to BM25 text query. | diff --git a/essentials/v2/semantic-search.mdx b/essentials/v2/semantic-search.mdx index a2430483..182e7996 100644 --- a/essentials/v2/semantic-search.mdx +++ b/essentials/v2/semantic-search.mdx @@ -70,7 +70,7 @@ curl -X POST 'https://api.hydradb.com/query' \ "alpha": 0.8, "recency_bias": 0.2, "graph_context": true, - "attributes": { "project": { "$eq": "phoenix" } } + "attributes": { "project": "phoenix" } }' ``` @@ -84,7 +84,7 @@ const result = await client.query({ alpha: 0.8, recencyBias: 0.2, graphContext: true, - attributes: { project: { $eq: "phoenix" } }, + attributes: { project: "phoenix" }, }); ``` @@ -98,7 +98,7 @@ result = client.query( alpha=0.8, recency_bias=0.2, graph_context=True, - attributes={"project": {"$eq": "phoenix"}}, + attributes={"project": "phoenix"}, ) ``` @@ -213,7 +213,7 @@ curl -X POST 'https://api.hydradb.com/query' \ "collection": "team-mobile", "query": "What is the current sprint status?", "query_by": "hybrid", - "attributes": { "project": { "$eq": "phoenix" } } + "attributes": { "project": "phoenix" } }' ``` @@ -223,7 +223,7 @@ const result = await client.query({ collection: "team-mobile", query: "What is the current sprint status?", queryBy: "hybrid", - attributes: { project: { $eq: "phoenix" } }, + attributes: { project: "phoenix" }, }); ``` @@ -233,7 +233,7 @@ result = client.query( collection="team-mobile", query="What is the current sprint status?", query_by="hybrid", - attributes={"project": {"$eq": "phoenix"}}, + attributes={"project": "phoenix"}, ) ``` diff --git a/essentials/v2/split-databases.mdx b/essentials/v2/split-databases.mdx index af8c2bcd..29e6d312 100644 --- a/essentials/v2/split-databases.mdx +++ b/essentials/v2/split-databases.mdx @@ -118,7 +118,7 @@ On a unified database, do not send `type`. `follow_forceful_relations` is the cu ### `metadata_filters` -`metadata_filters` is the older filter language. It still works on every database; new integrations should use [`attributes`](/essentials/v2/attributes), which supports `$eq`, `$in`, `$gt`, `$and`, `$or` and the rest. +`metadata_filters` is deprecated. It still works on every database and is still how you filter `custom_attributes`, nested under `additional_metadata`. For declared fields, use [`attributes`](/essentials/v2/attributes). Each declared (top-level) field in `metadata_filters` takes one operator object: diff --git a/get-started/v2/core-concepts.mdx b/get-started/v2/core-concepts.mdx index 73800562..99cf49d9 100644 --- a/get-started/v2/core-concepts.mdx +++ b/get-started/v2/core-concepts.mdx @@ -117,7 +117,7 @@ At query time: ```json { "query": "When is the SOC 2 report renewed?", - "attributes": { "compliance_framework": { "$eq": "SOC2" } } + "attributes": { "compliance_framework": "SOC2" } } ``` From c2b364045cb9de9dc993516f272bef874bf5c427 Mon Sep 17 00:00:00 2001 From: SohamRatnaparkhi Date: Thu, 24 Sep 2026 10:14:39 +0530 Subject: [PATCH 17/17] docs: drop the last operator and list-limit wording on attributes Signed-off-by: SohamRatnaparkhi Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.mdx | 2 +- api-reference/v2/endpoint/query.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.mdx b/AGENTS.mdx index 11ae146b..1b99128a 100644 --- a/AGENTS.mdx +++ b/AGENTS.mdx @@ -871,7 +871,7 @@ Rules: | `recency_bias` | `0.0` to `1.0` | Boost newer content. Default `0.4`; `0` disables it. | | `ids` | `string[]` | Restrict retrieval to these `context_id`s. | | `titles` | `string[]` | Restrict retrieval to context with one of these exact titles (case-insensitive). | -| `attributes` | object | Filter on declared attributes with operators. See [Filtering with attributes](#filtering-with-attributes). | +| `attributes` | object | Key-value pairs matched exactly against declared attributes. See [Filtering with attributes](#filtering-with-attributes). | | `acl` | `string[]` | Query on behalf of an identity: only context it may retrieve is returned. Omitted, empty or `["*"]` disables filtering. | | `query_apps` | boolean | Default `true`: also search connector content by its app identity (exact ids, actors, threads), on top of normal retrieval. | | `graph_context` | boolean | Default `true`: include `graph[]`. | diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index 86ae8ddf..436497ec 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -563,7 +563,7 @@ To show a chunk's graph paths under that chunk, group hops by `triplets[].relati Common codes: `400 INVALID_INPUT` (empty `query`), `400 VALIDATION_ERROR` (a malformed `attributes` filter), `404 DATABASE_NOT_FOUND`, `422 TENANT_INFRA_NOT_READY` (the database is still provisioning), `500 INTERNAL_ERROR`. See [Error Responses](/api-reference/v2/error-responses) for the full list. -`400` also covers oversized filters: an `attributes` list above 500 values, or an `attributes` object above 64 KiB of compact JSON. The message names the offending key or reports the actual byte count. See [Attributes](/essentials/v2/attributes). +`400` also covers an `attributes` object above 64 KiB of compact JSON. See [Attributes](/essentials/v2/attributes).