diff --git a/.audit/pro-2457-api-browser.json b/.audit/pro-2457-api-browser.json new file mode 100644 index 00000000..1195a574 --- /dev/null +++ b/.audit/pro-2457-api-browser.json @@ -0,0 +1,625 @@ +{ + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "method": "Local Mintlify browser navigation, rendered headings and field checks, nested schema expansion, table density and anchor checks. This is browser verification, not production endpoint execution.", + "pages": [ + { + "path": "api-reference/v2", + "title": "API Reference", + "status": "pass", + "issues": [], + "source_sha256": "3509aa7fa669e0c9c699881e8f13c5976ab963434f10a25a108daa68b15521b6", + "schema_sha256": null, + "rendered_field_count": 0, + "checked_at": "2026-10-21T19:06:36.884Z" + }, + { + "path": "api-reference/v2/endpoint/add-connector-resource", + "title": "Add Connector Resource", + "status": "pass", + "issues": [], + "source_sha256": "a4cd06f419264e866bbd4642837a6799b3f0b83985c2a20717179706d5a968bc", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 8, + "rendered_field_count": 51, + "checked_at": "2026-10-21T19:10:59.688Z" + }, + { + "path": "api-reference/v2/endpoint/configure-connector", + "title": "Configure Connector", + "status": "pass", + "issues": [], + "source_sha256": "f7c183814f2d8c3bd9c1a9d00411e97df493451f2fe04d903c90ddc6235c5d70", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 6, + "rendered_field_count": 37, + "checked_at": "2026-10-21T19:11:04.870Z" + }, + { + "path": "api-reference/v2/endpoint/connector-resources", + "title": "List Connector Resources", + "status": "pass", + "issues": [], + "source_sha256": "12c1df75ca2a8290d8181ce45394e6ab29487382bc66ddea50a631d3191b24f4", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 5, + "rendered_field_count": 33, + "checked_at": "2026-10-21T19:11:12.286Z" + }, + { + "path": "api-reference/v2/endpoint/connectors-overview", + "title": "Connectors: Overview", + "status": "pass", + "issues": [], + "source_sha256": "bc3a9b912fed60fe28b55aac0cdb23c6ff68cfeb83dd97e099f005deca802575", + "schema_sha256": null, + "rendered_field_count": 0, + "checked_at": "2026-10-21T19:05:50.281Z" + }, + { + "path": "api-reference/v2/endpoint/create-connector", + "title": "Create Connector", + "status": "pass", + "issues": [], + "source_sha256": "0bdd6e0f5b3dc5d0d94708b6b1beec84953522f919448accb4d46582c60a547b", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 1, + "rendered_field_count": 52, + "checked_at": "2026-10-21T19:11:15.708Z" + }, + { + "path": "api-reference/v2/endpoint/create-tenant", + "title": "Create Database", + "status": "pass", + "issues": [], + "source_sha256": "a90b3947aa4c21e43995da25687455c5f52239ef1026f4c9d1f4ab692dc7bafd", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 5, + "rendered_field_count": 46, + "checked_at": "2026-10-21T19:11:45.623Z" + }, + { + "path": "api-reference/v2/endpoint/delete-collection", + "title": "Delete Collection", + "status": "pass", + "issues": [], + "source_sha256": "1b5ce053e6004964dc2a42b5862147a7dfc9c2e258117c4cbaa12ccf75b63cd3", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 32, + "checked_at": "2026-10-21T19:11:49.804Z" + }, + { + "path": "api-reference/v2/endpoint/delete-connector", + "title": "Delete Connector", + "status": "pass", + "issues": [], + "source_sha256": "9fd690ae1ab13bbab64d362d323e68619d833bc98b780d89895755d0a51c1d1e", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 4, + "checked_at": "2026-10-21T19:11:55.560Z" + }, + { + "path": "api-reference/v2/endpoint/delete-connector-resource", + "title": "Delete Connector Resource", + "status": "pass", + "issues": [], + "source_sha256": "9f7e5f09be04893021cf66b01f9957a8c85c9af15a2f616316de078d75e17180", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 6, + "checked_at": "2026-10-21T19:11:59.712Z" + }, + { + "path": "api-reference/v2/endpoint/delete-source", + "title": "Delete Context", + "status": "pass", + "issues": [], + "source_sha256": "663ac6380724b59131d5cd05716bd23c22b46144a39e9a7ecc75e7835936e79f", + "schema_sha256": "e30ceedf81b894bec2831fdf4e6cec40a5aff2a3e57684c0f826f5a4ae6b6dc4", + "checked_at": "2026-10-21T20:16:47.128Z", + "rendered_field_count": 39, + "expanded_objects": 2 + }, + { + "path": "api-reference/v2/endpoint/delete-tenant", + "title": "Delete Database", + "status": "pass", + "issues": [], + "source_sha256": "1b58cbea010a23ac01d64338eed49ac5d7f1d16abda6305450eb768894201bfc", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 29, + "checked_at": "2026-10-21T19:12:28.871Z" + }, + { + "path": "api-reference/v2/endpoint/delete-webhook", + "title": "Delete Webhook", + "status": "pass", + "issues": [], + "source_sha256": "7c5a82981f1265d9f1c10131132eef3ae1f7786a889a576538e4e5a8571c9ab9", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 26, + "checked_at": "2026-10-21T19:12:32.912Z" + }, + { + "path": "api-reference/v2/endpoint/disable-webhook-signing", + "title": "Disable Signing", + "status": "pass", + "issues": [], + "source_sha256": "86cfdfc4ca12789aad2e6a4c816e0a4ba947c4289218cbb7728082d0720f4766", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 30, + "checked_at": "2026-10-21T19:12:36.847Z" + }, + { + "path": "api-reference/v2/endpoint/discover-connector-resources", + "title": "Discover Resources", + "status": "pass", + "issues": [], + "source_sha256": "5e3ea19fda0a849bb4d96e2d2d6556574f619df9b5012caa158c59b17f86ef15", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 13, + "checked_at": "2026-10-21T19:12:52.512Z" + }, + { + "path": "api-reference/v2/endpoint/fetch-content", + "title": "Inspect Context", + "status": "pass", + "issues": [], + "source_sha256": "87a01ce9d1b4507c28043a24df0c12188552a9768abc3e0dc4e799f15303842f", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 42, + "checked_at": "2026-10-21T19:12:55.262Z" + }, + { + "path": "api-reference/v2/endpoint/get-connector", + "title": "Get Connector", + "status": "pass", + "issues": [], + "source_sha256": "c6869e7309329cbc606f8dde3de1e607cfed8adc53172eea18119b9e4246f989", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 32, + "checked_at": "2026-10-21T19:13:00.060Z" + }, + { + "path": "api-reference/v2/endpoint/get-connector-status", + "title": "Get Connector Status", + "status": "pass", + "issues": [], + "source_sha256": "7b8c2d3530e4d8fb2f7dd6f1c32b7d876c9d672e6cdb9e2a0292e037c59e2334", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 1, + "rendered_field_count": 38, + "checked_at": "2026-10-21T19:13:05.641Z" + }, + { + "path": "api-reference/v2/endpoint/get-metadata-schema", + "title": "Get Metadata Schema", + "status": "pass", + "issues": [], + "source_sha256": "67214ced70b0fa2fbff616113e2dc5278a20b11ff9b9ae6344ada2e7dac0b384", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 1, + "rendered_field_count": 34, + "checked_at": "2026-10-21T19:13:38.502Z" + }, + { + "path": "api-reference/v2/endpoint/get-webhook", + "title": "Get Webhook", + "status": "pass", + "issues": [], + "source_sha256": "20cb99e95c4c778e8f037a4891384ce43476d81a358f7fe18177b6ed98d63d66", + "schema_sha256": "fbd5e12e839aa62c9560da14f342345b53f01107048395b57db374a60fe5e9b5", + "expanded_objects": 0, + "rendered_field_count": 28, + "checked_at": "2026-10-21T19:43:50.780Z" + }, + { + "path": "api-reference/v2/endpoint/get-webhook-delivery", + "title": "Get Delivery", + "status": "pass", + "issues": [], + "source_sha256": "eb387a151ff7bce24654ff61e759f093d9848406bcd32231e68cb1feb3ae9b42", + "schema_sha256": "72cd0ff4528a6ec2603ca151598b78d33d3af4de724708b7b55107078de1aff7", + "expanded_objects": 0, + "rendered_field_count": 36, + "checked_at": "2026-10-21T20:00:54.385Z" + }, + { + "path": "api-reference/v2/endpoint/ingest-context", + "title": "Ingest Context", + "status": "pass", + "issues": [], + "source_sha256": "b1e5a3d871e00b6399a17ba5fe700c32175ed6c4afa39d13bf227ee6044fe61a", + "schema_sha256": "72cd0ff4528a6ec2603ca151598b78d33d3af4de724708b7b55107078de1aff7", + "expanded_objects": 0, + "rendered_field_count": 49, + "checked_at": "2026-10-21T20:00:58.930Z" + }, + { + "path": "api-reference/v2/endpoint/list-connector-providers", + "title": "List Connector Providers", + "status": "pass", + "issues": [], + "source_sha256": "6ab366d622902049d914527cea63c626454ec128c91aeea76b33a415488d61a6", + "schema_sha256": "72cd0ff4528a6ec2603ca151598b78d33d3af4de724708b7b55107078de1aff7", + "expanded_objects": 0, + "rendered_field_count": 13, + "checked_at": "2026-10-21T20:01:01.153Z" + }, + { + "path": "api-reference/v2/endpoint/list-connectors", + "title": "List Connectors", + "status": "pass", + "issues": [], + "source_sha256": "c49eba6139960e2c792ab77ca3f4a47dd3f7e56e29307359d8131213e7886e3c", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 3, + "rendered_field_count": 45, + "checked_at": "2026-10-21T19:15:15.626Z" + }, + { + "path": "api-reference/v2/endpoint/list-documents", + "title": "List Context", + "status": "pass", + "issues": [], + "source_sha256": "b0297add480b155e0a7d1a36172ce4b864c96bb1f38f2abfdd0f248b12101b0e", + "schema_sha256": "e30ceedf81b894bec2831fdf4e6cec40a5aff2a3e57684c0f826f5a4ae6b6dc4", + "checked_at": "2026-10-21T20:16:49.415Z", + "rendered_field_count": 106, + "expanded_objects": 0 + }, + { + "path": "api-reference/v2/endpoint/list-sub-tenants", + "title": "List Collections", + "status": "pass", + "issues": [], + "source_sha256": "05fedb4f1cc5e0854405c2c5c7ce16ef98d7710ebd3ac53c07ddf35a1143d23a", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 28, + "checked_at": "2026-10-21T19:15:26.041Z" + }, + { + "path": "api-reference/v2/endpoint/list-tenants", + "title": "List Databases", + "status": "pass", + "issues": [], + "source_sha256": "747a9f7cf34ad1771902180c2b6c018df1ac441278a5fe7a4c9b4a7627db4a06", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 2, + "rendered_field_count": 35, + "checked_at": "2026-10-21T19:17:22.766Z" + }, + { + "path": "api-reference/v2/endpoint/list-webhook-deliveries", + "title": "List Deliveries", + "status": "pass", + "issues": [], + "source_sha256": "19a266756cdbc471c1b19a5d42f473be144820d74a2dbe8f7d0fc76de8ea37b8", + "schema_sha256": "72cd0ff4528a6ec2603ca151598b78d33d3af4de724708b7b55107078de1aff7", + "expanded_objects": 0, + "rendered_field_count": 41, + "checked_at": "2026-10-21T20:01:26.232Z" + }, + { + "path": "api-reference/v2/endpoint/pause-connector", + "title": "Pause Connector", + "status": "pass", + "issues": [], + "source_sha256": "907b7a1a3399fa24bd7839348d06cf252abe4e3f747af3373c6b4355e153a598", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 5, + "checked_at": "2026-10-21T19:17:31.748Z" + }, + { + "path": "api-reference/v2/endpoint/query", + "title": "Query", + "status": "pass", + "issues": [], + "source_sha256": "fe2baaf1aa04383e70f879ebdba24136246c9aada9eddae023ba9dd42dadcb84", + "schema_sha256": "72cd0ff4528a6ec2603ca151598b78d33d3af4de724708b7b55107078de1aff7", + "expanded_objects": 7, + "rendered_field_count": 211, + "checked_at": "2026-10-21T20:01:29.349Z" + }, + { + "path": "api-reference/v2/endpoint/query-overview", + "title": "Query: Overview", + "status": "pass", + "issues": [], + "source_sha256": "8e014653da4f3d3d98d433cad35ef48d3d35fc65e99604be48c4c10b09fd21d4", + "schema_sha256": null, + "rendered_field_count": 1, + "checked_at": "2026-10-21T19:05:52.076Z" + }, + { + "path": "api-reference/v2/endpoint/register-webhook", + "title": "Register Webhook", + "status": "pass", + "issues": [], + "source_sha256": "9ea2976c4f2e0c0c8a98f101d56ebe105da0453d500ae8a607a7a95c8635639d", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 2, + "rendered_field_count": 34, + "checked_at": "2026-10-21T19:22:02.064Z" + }, + { + "path": "api-reference/v2/endpoint/resume-connector", + "title": "Resume Connector", + "status": "pass", + "issues": [], + "source_sha256": "0d7176aedc7390d179fb2c651a1bd3e65674a28081c1b27dc6f5d1fec2d85bbd", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 5, + "checked_at": "2026-10-21T19:22:04.280Z" + }, + { + "path": "api-reference/v2/endpoint/retry-webhook-delivery", + "title": "Retry Delivery", + "status": "pass", + "issues": [], + "source_sha256": "1b6183a11bd945e5d78e4c8ce5aaff63d1ab7a4c9235fc31f46bade670f83743", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 28, + "checked_at": "2026-10-21T19:22:06.576Z" + }, + { + "path": "api-reference/v2/endpoint/set-webhook-signing-secret", + "title": "Set Signing Secret", + "status": "pass", + "issues": [], + "source_sha256": "7ee323be35781f7a0b7d5a1475b059b86ac68d4bf89ec4fc97e5335bb76ada6e", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 28, + "checked_at": "2026-10-21T19:22:09.320Z" + }, + { + "path": "api-reference/v2/endpoint/source-relations", + "title": "Inspecting Context Relations", + "status": "pass", + "issues": [], + "source_sha256": "81783b0ac1a350cdce3a18340119075b8fc3f540f179259b6c44b45352a147cc", + "schema_sha256": "72cd0ff4528a6ec2603ca151598b78d33d3af4de724708b7b55107078de1aff7", + "expanded_objects": 0, + "rendered_field_count": 100, + "checked_at": "2026-10-21T20:01:35.143Z" + }, + { + "path": "api-reference/v2/endpoint/source-status", + "title": "Ingestion Status", + "status": "pass", + "issues": [], + "source_sha256": "0fe0e59cbf65a95bb7a165a83539001c29e7389a32518ed550f3ba9ec6f0aae5", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 1, + "rendered_field_count": 37, + "checked_at": "2026-10-21T19:22:33.915Z" + }, + { + "path": "api-reference/v2/endpoint/sources-overview", + "title": "Context Management: Overview", + "status": "pass", + "issues": [], + "source_sha256": "23a2c5bd259348ba3656b41a044fb387d31b5ea0d110e662f52e1443b52b4439", + "schema_sha256": null, + "rendered_field_count": 0, + "checked_at": "2026-10-21T19:05:57.061Z" + }, + { + "path": "api-reference/v2/endpoint/subgraph", + "title": "Connected Subgraph", + "status": "pass", + "issues": [], + "source_sha256": "632a8ba01bcbf82c2ddae590870e3e8c1091de5fd2abbbc00e4e1838ab07cbd1", + "schema_sha256": "3e3a79b41dfbba6a5002e0bfbbbce7447f9ecfc905ebaefaa66422b30b979ade", + "expanded_objects": 1, + "rendered_field_count": 112, + "checked_at": "2026-10-21T20:08:27.014Z" + }, + { + "path": "api-reference/v2/endpoint/submit-feedback", + "title": "Submit Feedback", + "status": "pass", + "issues": [], + "source_sha256": "e931353c38596dccb1507d162cc53e41d6503689f8e1e639d22f97bcaaf28efd", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 1, + "rendered_field_count": 42, + "checked_at": "2026-10-21T19:22:40.642Z" + }, + { + "path": "api-reference/v2/endpoint/sync-connector", + "title": "Sync Connector", + "status": "pass", + "issues": [], + "source_sha256": "707601842f9eaf53c3b249eb83f2e17f9b9ef4518509e8bb20292e7315f38df0", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 4, + "checked_at": "2026-10-21T19:23:12.563Z" + }, + { + "path": "api-reference/v2/endpoint/tenant-stats", + "title": "Database Stats", + "status": "pass", + "issues": [], + "source_sha256": "2a54bdec4c15c463bab69dcd5b3df738615f3cf9cea35da4154c4eaab439ccbc", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 2, + "rendered_field_count": 32, + "checked_at": "2026-10-21T19:23:13.966Z" + }, + { + "path": "api-reference/v2/endpoint/tenant-status", + "title": "Database Status", + "status": "pass", + "issues": [], + "source_sha256": "3e44912d0c21d37c21196dff0baf690c940c8c6ed171d093635f64c9d54227c0", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 2, + "rendered_field_count": 36, + "checked_at": "2026-10-21T19:23:17.253Z" + }, + { + "path": "api-reference/v2/endpoint/tenants-overview", + "title": "Databases: Overview", + "status": "pass", + "issues": [], + "source_sha256": "d27b863b8225b717f273b52a9ab2ce8114d068d6f082ab5dedb7b1347ad02ded", + "schema_sha256": null, + "rendered_field_count": 0, + "checked_at": "2026-10-21T19:06:14.545Z" + }, + { + "path": "api-reference/v2/endpoint/test-webhook", + "title": "Send Test Delivery", + "status": "pass", + "issues": [], + "source_sha256": "b96852985e18f3a5c1b2f2eae57e7d07cdc2955391c0ba5031782ae3b8c4bb18", + "schema_sha256": "fbd5e12e839aa62c9560da14f342345b53f01107048395b57db374a60fe5e9b5", + "expanded_objects": 0, + "rendered_field_count": 27, + "checked_at": "2026-10-21T19:44:03.766Z" + }, + { + "path": "api-reference/v2/endpoint/update-connector", + "title": "Update Connector", + "status": "pass", + "issues": [], + "source_sha256": "feafd783038619ca39ec050650fd1534286bc872162b083b81a754111f8f7f8a", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 13, + "checked_at": "2026-10-21T19:23:43.551Z" + }, + { + "path": "api-reference/v2/endpoint/update-connector-resource", + "title": "Update Connector Resource", + "status": "pass", + "issues": [], + "source_sha256": "ea971000f1f1768a97b4d4066beaf06a315c09a8abd345e7b7da4ccfc44cd72a", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 0, + "rendered_field_count": 10, + "checked_at": "2026-10-21T19:23:44.832Z" + }, + { + "path": "api-reference/v2/endpoint/update-metadata-schema", + "title": "Update Metadata Schema", + "status": "pass", + "issues": [], + "source_sha256": "4d1037c3a3b6b147979d9799c0254988ca334ad7ef3746487ce6018ef5aa5752", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 1, + "rendered_field_count": 12, + "checked_at": "2026-10-21T19:23:48.174Z" + }, + { + "path": "api-reference/v2/endpoint/update-source-metadata", + "title": "Update Source Metadata", + "status": "pass", + "issues": [], + "source_sha256": "8a637b552323e6bc66d086920e6f5feaef514b83182c42298cace5b9b1ad0166", + "schema_sha256": "fc8da407267e6581a07955997f07224f74c6c8df6ab5c82433d53acaa2e40dd4", + "expanded_objects": 3, + "rendered_field_count": 59, + "checked_at": "2026-10-21T19:23:51.055Z" + }, + { + "path": "api-reference/v2/endpoint/webhooks-overview", + "title": "Webhooks: Overview", + "status": "pass", + "issues": [], + "source_sha256": "24bca7f5d4465aaf2fa9d6e86e7b0922855f6f3ee7be02b593c775602afef36d", + "schema_sha256": null, + "rendered_field_count": 0, + "checked_at": "2026-10-21T19:06:16.036Z" + }, + { + "path": "api-reference/v2/error-responses", + "title": "Error Responses", + "status": "pass", + "issues": [], + "source_sha256": "e7ac23e063ce031c55532da2e52449898baf6e2396405201341cd0deb7916452", + "schema_sha256": null, + "rendered_field_count": 1, + "checked_at": "2026-10-21T19:06:19.253Z" + }, + { + "path": "api-reference/v2/sdks", + "title": "SDKs: Python and TypeScript", + "status": "pass", + "issues": [], + "source_sha256": "ce6a2d06c21e297a840e8987c625e774cf41eb40352d281690644b072d647f4e", + "schema_sha256": null, + "rendered_field_count": 1, + "checked_at": "2026-10-21T19:06:38.252Z" + }, + { + "path": "essentials/v2/api-results", + "title": "How to Use API Results", + "status": "pass", + "issues": [], + "source_sha256": "bc6a53f07e45fa0c11fc86eae0794e3e92b53f6dc78332281a8cd27ebd7ae506", + "schema_sha256": null, + "rendered_field_count": 2, + "checked_at": "2026-10-21T19:06:42.606Z" + } + ], + "final_schema_sha256": "e30ceedf81b894bec2831fdf4e6cec40a5aff2a3e57684c0f826f5a4ae6b6dc4", + "subsequent_schema_change": { + "operation": "GET /context/subgraph", + "change": "Removed the unknown-ID behavior sentence from the query-parameter alias description. All other operations and schemas are byte-for-byte equivalent after JSON parsing. The rendered Connected Subgraph page binds to GET /context/{id}/subgraph.", + "verification": "Parsed comparison accepts exactly this description change. All 44 bound operations and their schemas are unchanged." + }, + "nullability_rechecks": { + "pages": [ + "api-reference/v2/endpoint/get-webhook", + "api-reference/v2/endpoint/get-webhook-delivery", + "api-reference/v2/endpoint/list-connector-providers", + "api-reference/v2/endpoint/list-webhook-deliveries", + "api-reference/v2/endpoint/test-webhook" + ], + "reason": "Webhook and catalog fields now include null. Comparing each of the 44 bound operations and all referenced schemas identified these five affected pages. Each passed a new browser visit; the remaining bound operations and schemas are unchanged.", + "checked_at": "2026-10-21T19:45:16.871Z" + }, + "review_rechecks": { + "pages": [ + "api-reference/v2/endpoint/delete-source", + "api-reference/v2/endpoint/get-webhook-delivery", + "api-reference/v2/endpoint/ingest-context", + "api-reference/v2/endpoint/list-connector-providers", + "api-reference/v2/endpoint/list-documents", + "api-reference/v2/endpoint/list-webhook-deliveries", + "api-reference/v2/endpoint/query", + "api-reference/v2/endpoint/source-relations", + "api-reference/v2/endpoint/subgraph" + ], + "reason": "Provider authorization, released corpus selectors, ingestion batch success, webhook examples, graph examples, metadata filter limits and memory deletion count corrections. Operation and referenced-schema comparison identified these nine affected pages. Each passed a new browser visit. The other 35 bound operations and their schemas are unchanged.", + "checked_at": "2026-10-21T20:02:55.097Z" + }, + "final_wording_recheck": { + "page": "api-reference/v2/endpoint/subgraph", + "reason": "Both subgraph routes now use one shared corpus description. Parsed schema comparison identifies only this bound page as changed.", + "checked_at": "2026-10-21T20:08:27.014Z" + }, + "combined_context_recheck": { + "pages": [ + "api-reference/v2/endpoint/delete-source", + "api-reference/v2/endpoint/list-documents" + ], + "reason": "List and Delete preserve the released all selector. Operation and referenced-schema comparison identifies only these two bound pages as changed.", + "checked_at": "2026-10-21T20:16:49.415Z" + } +} diff --git a/.audit/pro-2457-api-reference-decisions.tsv b/.audit/pro-2457-api-reference-decisions.tsv new file mode 100644 index 00000000..23b8947e --- /dev/null +++ b/.audit/pro-2457-api-reference-decisions.tsv @@ -0,0 +1,31 @@ +ts phase decision why evidence result +2026-10-21T18:37:14.055494+00:00 frame Review all 53 V2 API pages and both public schema exports The user needs readable docs and SDK contracts without internal fields pages.json baseline captured +2026-10-21T18:37:14.055690+00:00 workspace Use a separate application checkout under the T3 worktrees directory /workspace is read-only on this Mac; avoid changing the existing application branch /Users/soham/.t3/worktrees/hydradb-application/pr310-public-api-20261021 clean branch from staging 2e8622bc4 +2026-10-21T18:37:14.055696+00:00 research Keep the memory deletion runtime unchanged Current handler already returns per-ID results; stale docs caused the mismatch application/internal/api/handler/context.go:1203 focused HTTP tests passed +2026-10-21T18:37:14.055712+00:00 design Derive docs and SDK from the same released contract The current pre-withholding fork leaks different fields into each export application/scripts/openapi/main.go:110 implementation pending +2026-10-21T18:48:09.016388+00:00 design Keep attributes and temporal_now excluded despite the second design reviewer's contrary advice The user explicitly said both fields must be hidden and metadata remains supported user instruction; pr310-public-contract-design-grok-20261022-r1 original contract requirements retained +2026-10-21T18:48:49.641525+00:00 harness Add a CI check for public query fields, nested graph types, page bindings, JSON examples, and empty anchors Recurring schema and documentation mistakes should fail a check rather than rely on another instruction .github/verify_v2_public_contract.py old schema fails 12 requested contract checks +2026-10-21T18:48:49.641822+00:00 verification Reserve full 53-page browser coverage for the final regenerated schema Complete source baseline and reproduced visual bugs cover the original state without duplicating the final run baseline-docs.json; browser-baseline-grok/raw.jsonl initial browser pass bounded +2026-10-21T18:56:05.856360+00:00 baseline Treat initial browser receipts as issue evidence rather than a clean before snapshot 48 pages loaded but two served pages did not match the on-disk baseline; final coverage will use one server browser-baseline-grok/_coverage.json INCONCLUSIVE for complete baseline parity +2026-10-21T18:58:44.126111+00:00 review Remove query fusion and temporal-filter diagnostic blocks from both public outputs These fields expose internal ranking accounting; keep the returned dated facts and durations application/internal/domain/search/app_search_fusion.go; application/internal/domain/search/temporal.go runtime unchanged; export update in progress +2026-10-21T19:09:54.812201+00:00 verification Restart the docs preview after changing the OpenAPI snapshot The preview still showed the old non-nullable error field after the JSON file changed api-reference/v2/openapi.json; final-dev.log endpoint browser checks wait for fresh preview +2026-10-21T19:29:18.118553+00:00 review Keep unknown-ID subgraph behavior out of the prose The user explicitly requested removing that Notes section; restoring it would undo the requested simplification pr310-public-editorial-review-grok-20261022-r1; endpoint/subgraph.mdx review finding dismissed with user instruction +2026-10-21T19:29:18.119007+00:00 review Fix five editorial review findings and strengthen the contract guard Preserve canonical metadata, correct title fields, keep indexing batch behavior, and validate nested schemas pr310-public-editorial-review-codex-20261022-r1; pr310-public-contract-fix-verifier-20261022-r2 all findings fixed; final request-example guard verified in r3 +2026-10-21T19:29:18.119013+00:00 verification Validate all handwritten response payloads against their endpoint schemas JSON syntax alone missed real nullable response fields verify_v2_public_contract.py; application/scripts/openapi/public_shapes.go 58 response examples pass after source schema correction +2026-10-21T19:29:18.119017+00:00 verification Inspect every V2 API page against the final schema The user requested browser inspection of each page, including nested fields .audit/pro-2457-api-browser.json 53 pages pass; 44 endpoint bindings; two edited pages rechecked +2026-10-21T19:29:18.119021+00:00 verification Exercise generated TypeScript deserialization in SDK helper tests Typed relation fields use camelCase and previously lost relationship labels application/scripts/fern/test_helpers.py six formatter tests pass including generated serializers +2026-10-21T19:29:18.119024+00:00 verification Report full local SDK generation as failed Both Fern container runs exited 137 despite producing the model and serializer files /tmp/pr310-sdk-generation-parent-rerun.log; application/.audit/public-api-handoff.txt not a full generation pass; no SDK was published +2026-10-21T19:29:18.119029+00:00 verification Keep V1 and archive anchor warnings outside this V2 task The final link scan found four pre-existing anchor warnings outside V2 /tmp/pr310-public-contract-20261021/final-links.log no V2 link findings; Mintlify validation and hygiene pass +2026-10-21T19:33:31.128304+00:00 verification Remove the remaining unknown-ID subgraph sentence from the source export The user requested omitting this implementation quirk application/scripts/openapi/docs_view_text_core.go; .audit/pro-2457-api-browser.json only unbound alias description changed; 44 bound operations and schemas unchanged +2026-10-21T19:36:49.010049+00:00 verification Diagnose SDK generation failure with a retained container Avoid guessing that exit 137 means memory exhaustion docker inspect f003e9d47066 State.OOMKilled=true ExitCode=137 confirmed local Docker OOM; bounded generator heap trial in progress +2026-10-21T19:46:55.137877+00:00 review Fix the two independent source review warnings Preserve free-text dates and null names, and model null webhook/catalog values accurately application commit 69b4ad16b; failing then passing focused fixtures generator tests and eight SDK helper tests pass +2026-10-21T19:46:55.138179+00:00 verification Complete SDK generation with a bounded Node heap The retained stock container confirms Docker OOM; the same pinned generator filesystem completes within a smaller heap .audit/pro-2457-sdk-generation.json full Fern CLI generation and generated-code checks pass; no published config change +2026-10-21T19:46:55.138184+00:00 verification Recheck all pages affected by nullable schema corrections Operation and referenced-schema comparison identifies five pages with changed rendered contracts .audit/pro-2457-api-browser.json five new browser checks pass; all 53 raw receipts match summary +2026-10-21T19:46:55.138188+00:00 status Resolve earlier implementation-pending and export-in-progress log entries Those rows describe the state when recorded, not the final result application commit 69b4ad16b; docs current snapshot shared source policy, nested types, public descriptions and docs snapshot implemented +2026-10-21T20:02:56.000358+00:00 review Restore provider catalog authentication in the source annotation The auth-only router requires a token; the generated contract omitted it application commit a3272cf71; local HTTP generated-client test old SDK test fails without auth; regenerated SDK sends Bearer token +2026-10-21T20:02:56.000948+00:00 review Publish two corpus choices outside Query Keep released endpoint instructions and generated fields consistent; graph all does not combine corpora withholdUnifiedSurface; TestReleasedContextSelectorsAndProviderAuth knowledge and memory published; Query retains all +2026-10-21T20:02:56.000955+00:00 review Correct generated response semantics and examples Batch success can differ from request acceptance; memory deletion is a count; successful webhook errors are null application commit a3272cf71 source descriptions and examples corrected +2026-10-21T20:02:56.000959+00:00 review Preserve metadata limits and connected graph examples Shortening must retain enforced limits, distinct entity names and matching relation endpoints TestPublishedQueryGraphExamplesStayConnected; shared metadataFiltersDescription 500 values per list and 64 KiB retained; scoped graph examples stay connected +2026-10-21T20:02:56.000963+00:00 verification Recheck all nine pages affected by the review fixes Compare each operation and its schema references to avoid assuming the rest changed .audit/pro-2457-api-browser.json nine browser rechecks pass; 53 total receipts remain consistent +2026-10-21T20:09:27.184100+00:00 review Use one corpus description for the graph routes The path alias retained a third choice after its enum was narrowed application commit 1db00c82f; docs CI guard; subgraph browser recheck both subgraph forms and relations now use the same two-choice description; no schema shape change +2026-10-21T20:18:39.372933+00:00 compatibility Preserve combined List and Delete requests Existing split-database handlers support all without the unified feature flag. Hide unified while retaining all for these two endpoints. Graph reads keep knowledge and memory. Source regression red then green; 10 SDK tests pass; complete Fern generation passes; two changed pages browser-checked. Fixed published enum regression. diff --git a/.audit/pro-2457-sdk-generation.json b/.audit/pro-2457-sdk-generation.json new file mode 100644 index 00000000..221c557f --- /dev/null +++ b/.audit/pro-2457-sdk-generation.json @@ -0,0 +1,22 @@ +{ + "generator": "fernapi/fern-typescript-sdk:3.69.0", + "base_image_id": "sha256:abba03d3eb4400a0093ed4ed790a939164fcfcb1e75f314e60b0250262a54199", + "temporary_image_id": "sha256:de3e013e9871ae1718d5b6bec8b426f0f0fb2740a620331705d18b800ebba3fd", + "filesystem_layers_identical": true, + "entrypoint_identical": true, + "only_environment_difference": "NODE_OPTIONS=--max-old-space-size=384", + "ir_version": "v59", + "openapi_input_sha256": "0a9f515722c8c88e758e5e04d41fbdf5214e229b1bbe6e55c9587451d5214410", + "full_fern_cli_exit_code": 0, + "generated_code_checks": "passed", + "sdk_helper_tests": 10, + "sdk_helper_skips": 0, + "includes_generated_null_preservation_test": true, + "stock_generator_attempts": "Docker OOMKilled=true, exit 137", + "repository_generator_config_changed": false, + "sdk_published": false, + "local_generation_log": "/tmp/pr310-combined-sdk-generation.log", + "local_helper_log": "/tmp/pr310-combined-sdk-helper.log", + "includes_generated_provider_auth_http_test": true, + "includes_generated_combined_selector_test": true +} diff --git a/.github/verify_v2_public_contract.py b/.github/verify_v2_public_contract.py new file mode 100644 index 00000000..d4f49c58 --- /dev/null +++ b/.github/verify_v2_public_contract.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +import json +import pathlib +import re +import sys + +from jsonschema import Draft202012Validator +from referencing import Registry, Resource +from referencing.jsonschema import DRAFT202012 + + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SPEC_PATH = ROOT / "api-reference/v2/openapi.json" +SPEC = json.loads(SPEC_PATH.read_text()) +SCHEMAS = SPEC["components"]["schemas"] +ERRORS = [] +REGISTRY = Registry().with_resource( + "urn:hydradb:openapi", + Resource.from_contents(SPEC, default_specification=DRAFT202012), +) + + +def require(condition, message): + if not condition: + ERRORS.append(message) + + +def walk(value, path="$ public spec"): + if isinstance(value, dict): + if "$ref" in value: + ref = value["$ref"] + require(ref.startswith("#/"), f"{path}: external reference {ref}") + if ref.startswith("#/"): + target = SPEC + for part in ref[2:].split("/"): + key = part.replace("~1", "/").replace("~0", "~") + if not isinstance(target, dict) or key not in target: + ERRORS.append(f"{path}: unresolved reference {ref}") + break + target = target[key] + for key, child in value.items(): + if key not in {"example", "examples"}: + walk(child, f"{path}.{key}") + elif isinstance(value, list): + for index, child in enumerate(value): + walk(child, f"{path}[{index}]") + + +def resolve(schema): + seen = set() + while "$ref" in schema: + ref = schema["$ref"] + if ref in seen or not ref.startswith("#/components/schemas/"): + return {} + seen.add(ref) + schema = SCHEMAS.get(ref.rsplit("/", 1)[-1], {}) + return schema + + +def object_shape(schema): + schema = resolve(schema) + if schema.get("type") == "object" or "additionalProperties" in schema: + return schema + for branch in schema.get("oneOf", []) + schema.get("anyOf", []): + found = object_shape(branch) + if found is not None: + return found + return None + + +walk(SPEC) +query = SCHEMAS["search.QueryRequest"]["properties"] +for field in ("attributes", "graph_vector_prune", "graph_vector_prune_spacy_entities", "temporal_intent", "temporal_now"): + require(field not in query, f"QueryRequest exposes internal field {field}") +for field in ("metadata_filters", "graph_context", "response_format", "temporal_reasoning"): + require(field in query, f"QueryRequest is missing released field {field}") +require(not query["metadata_filters"].get("deprecated"), "metadata_filters must remain supported") +require(query["temporal_reasoning"].get("default") is True, "temporal_reasoning must default to true") +list_filters = SCHEMAS["list.ContentFilter"]["properties"] +require("attributes" not in list_filters, "List Context must not expose unlaunched attributes") +require("metadata" in list_filters, "List Context must preserve filters.metadata") +require(not list_filters.get("metadata", {}).get("deprecated"), "List Context filters.metadata must remain supported") +result = SCHEMAS["search.V2RetrievalResult"]["properties"] +for field in ("source_facts", "source_filter", "sources", "app_search_fusion", "temporal_filter"): + require(field not in result, f"Query result exposes internal field {field}") +for field in ("chunks", "graph_context", "additional_context", "llm_prompt"): + require(field in result, f"Query result is missing released field {field}") +triplet = SCHEMAS["search.PathTriplet"]["properties"] +for field in ("source", "target", "relation"): + child = resolve(triplet[field]) + require(bool(child.get("properties")), f"PathTriplet.{field} must expose its nested fields") +for schema_name, schema in SCHEMAS.items(): + for field in ("metadata", "additional_metadata", "provider_metadata", "credentials"): + if field in schema.get("properties", {}): + shape = object_shape(schema["properties"][field]) + require(shape is not None and "additionalProperties" in shape and shape["additionalProperties"] is not False, + f"{schema_name}.{field} must preserve arbitrary customer keys") +security = SPEC["components"]["securitySchemes"]["BearerAuth"] +require("prefix.secret" not in security.get("description", ""), "Bearer auth must describe the user's API key") +providers = SPEC["paths"]["/connectors/providers"]["get"] +require(any("BearerAuth" in rule for rule in providers.get("security", [])), "Provider catalog requires Bearer authentication") +for name in ("list.V2ListContentRequest", "sources.V2SourceDeleteRequest"): + require(SCHEMAS[name]["properties"]["type"].get("enum") == ["knowledge", "memory", "all"], f"{name}: preserve combined knowledge and memory requests") +for path in ("/context/relations", "/context/{id}/subgraph", "/context/subgraph"): + for parameter in SPEC["paths"][path]["get"].get("parameters", []): + if parameter.get("name") == "type": + require(parameter["schema"].get("enum") == ["knowledge", "memory"], f"{path}: publish knowledge and memory selectors") + require("`all`" not in parameter.get("description", ""), f"{path}: description must match the published selectors") +require("all" in SCHEMAS["search.SourceType"].get("enum", []), "Query must retain combined knowledge and memory retrieval") +filter_text = query["metadata_filters"].get("description", "") +require("500" in filter_text and "64 KiB" in filter_text, "Query filters must document enforced list and byte limits") +graph_source = triplet["source"].get("example", {}) +graph_target = triplet["target"].get("example", {}) +graph_relation = triplet["relation"].get("example", {}) +require(graph_source.get("name") and graph_target.get("name") and graph_source.get("entity_id") != graph_target.get("entity_id"), "Graph examples need distinct named entities") +require(graph_source.get("entity_id") == graph_relation.get("source_entity_id") and graph_target.get("entity_id") == graph_relation.get("target_entity_id"), "Graph example relation must reference its source and target") + +internal_fields = {"attributes", "graph_vector_prune", "graph_vector_prune_spacy_entities", "temporal_intent", "temporal_now"} +legacy_fields = {"tenant_id", "sub_tenant_id", "sub_tenant_ids", "tenant_metadata_keys", "milvus_sync_required", "milvus_synced", "milvus_rows_synced"} +pages = sorted((ROOT / "api-reference/v2").rglob("*.mdx")) + [ROOT / "essentials/v2/api-results.mdx"] +operation_count = 0 +json_count = 0 +response_count = 0 +for page in pages: + source = page.read_text() + relative = page.relative_to(ROOT) + require(not re.search(r'', source), f"{relative}: empty link anchor adds an orange rule") + require(not re.search(r'(.*?)", source, re.S): + for label, body in re.findall(r"```json([^\n]*)\n(.*?)\n```", block, re.S): + try: + sample = json.loads(body) + except json.JSONDecodeError: + continue + success = sample.get("success") if isinstance(sample, dict) else None + if success is None and not re.match(r"\s*2\d\d", label): + continue + candidates = [] + for code, response in operation.get("responses", {}).items(): + positive = str(code).startswith("2") + if positive != (success is not False): + continue + schema = response.get("content", {}).get("application/json", {}).get("schema") + if schema: + candidates.append({"$ref": f"urn:hydradb:openapi#/paths/{path.replace('~', '~0').replace('/', '~1')}/{method.lower()}/responses/{code}/content/application~1json/schema"}) + if candidates: + response_count += 1 + attempts = [list(Draft202012Validator(candidate, registry=REGISTRY).iter_errors(sample)) for candidate in candidates] + findings = min(attempts, key=len) + details = "; ".join(f"{error.json_path}: {error.message[:120]}" for error in findings[:4]) + require(not findings, f"{relative}: response example {label.strip()} does not match the published schema: {details}") + for line in source.splitlines(): + if line.lstrip().startswith("|"): + field = re.search(r' +```python Python SDK +client.connectors.create_resource( + "{connector_id}", + resource_id="C0123456789", + resource_type="channel", + display_name="general", + filters={"lookback_days": 30}, +) +``` + +```typescript TypeScript SDK +await client.connectors.createResource({ + id: "{connector_id}", + resourceId: "C0123456789", + resourceType: "channel", + displayName: "general", + filters: { lookback_days: 30 }, +}); +``` + ```bash cURL curl -X POST 'https://api.hydradb.com/connectors/{id}/resources' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -23,7 +43,6 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/resources' \ "resource_id": "C0123456789", "resource_type": "channel", "display_name": "general", - "collection_override": "all-hands", "filters": { "lookback_days": 30 } }' ``` @@ -41,9 +60,9 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/resources' \ "status": "active", "provider_cursor": "", "database_override": "", - "collection_override": "all-hands", + "collection_override": "", "tenant_id_override": "", - "sub_tenant_id_override": "all-hands", + "sub_tenant_id_override": "", "provider_metadata": null, "filters": { "lookback_days": 30 @@ -58,13 +77,13 @@ Take `resource_id` and `resource_type` from [Discover Resources](/api-reference/ ## Errors - `400`: a field is invalid or not accepted. -- `404`: no connector with this id in your workspace. +- `404`: no connector with this id in your organization. - `409`: the connector already has a resource with this `resource_id`. Nothing was changed. - `422`: HydraDB tested the resource and the provider refused access. The message says why.
-## Related Resources +## Related resources - [Update Connector Resource](/api-reference/v2/endpoint/update-connector-resource): change an existing resource - [Configure Connector](/api-reference/v2/endpoint/configure-connector): add several resources and start a sync diff --git a/api-reference/v2/endpoint/configure-connector.mdx b/api-reference/v2/endpoint/configure-connector.mdx index 4e0fa988..c84a34b5 100644 --- a/api-reference/v2/endpoint/configure-connector.mdx +++ b/api-reference/v2/endpoint/configure-connector.mdx @@ -18,12 +18,54 @@ You can call configure again at any time to add resources or change their settin - The resource's sync position is kept, so it does not sync again from the beginning. - `custom_instructions` and `acl` are kept when you omit them. To clear instructions, send `""` with [Update Connector Resource](/api-reference/v2/endpoint/update-connector-resource); configure cannot clear them. - - To change only a resource's instructions or access rule, use [Update Connector Resource](/api-reference/v2/endpoint/update-connector-resource) instead. It changes just the fields you send. - +To change only a resource's instructions or access rule, use [Update Connector Resource](/api-reference/v2/endpoint/update-connector-resource) instead. It changes just the fields you send. +```python Python SDK +result = client.connectors.configure( + "{connector_id}", + lookback_days=30, + resources=[ + { + "resource_id": "C0123456789", + "resource_type": "channel", + "name": "general", + "metadata": {"department": "all-hands"}, + "additional_metadata": {"internal_label": "general-slack"}, + }, + { + "resource_id": "C0987654321", + "resource_type": "channel", + "name": "incidents", + "custom_instructions": "These threads are incident retros. Extract root cause, impact and owner.", + }, + ], +) +``` + +```typescript TypeScript SDK +const result = await client.connectors.configure({ + id: "{connector_id}", + lookbackDays: 30, + resources: [ + { + resourceId: "C0123456789", + resourceType: "channel", + name: "general", + metadata: { department: "all-hands" }, + additionalMetadata: { internal_label: "general-slack" }, + }, + { + resourceId: "C0987654321", + resourceType: "channel", + name: "incidents", + customInstructions: "These threads are incident retros. Extract root cause, impact and owner.", + }, + ], +}); +``` + ```bash cURL curl -X POST 'https://api.hydradb.com/connectors/{id}/configure' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -36,7 +78,6 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/configure' \ "resource_id": "C0123456789", "resource_type": "channel", "name": "general", - "collection": "all-hands", "metadata": { "department": "all-hands" }, "additional_metadata": { "internal_label": "general-slack" } }, @@ -44,7 +85,6 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/configure' \ "resource_id": "C0987654321", "resource_type": "channel", "name": "incidents", - "collection": "engineering", "custom_instructions": "These threads are incident retros. Extract root cause, impact and owner." } ] @@ -74,14 +114,14 @@ Use [Get Connector Status](/api-reference/v2/endpoint/get-connector-status) to f ## Errors - `400`: `resources` is empty, or a field is invalid, for example `custom_instructions` over 4,000 characters. -- `404`: no connector with this id in your workspace. +- `404`: no connector with this id in your organization. - `422`: HydraDB tested a resource and the provider refused access. The message says which one and why. Nothing is saved.
-## Related Resources +## Related resources - **Next:** [Get Connector Status](/api-reference/v2/endpoint/get-connector-status): follow the first sync - [Update Connector Resource](/api-reference/v2/endpoint/update-connector-resource): change one resource's instructions or access rule - [Discover Resources](/api-reference/v2/endpoint/discover-connector-resources): find resource ids before configuring -- [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview): how synced metadata is merged +- [Connectors: Overview](/api-reference/v2/endpoint/connectors-overview): how synced metadata is merged diff --git a/api-reference/v2/endpoint/connector-resources.mdx b/api-reference/v2/endpoint/connector-resources.mdx index 21db3c57..d4bb2097 100644 --- a/api-reference/v2/endpoint/connector-resources.mdx +++ b/api-reference/v2/endpoint/connector-resources.mdx @@ -12,6 +12,14 @@ The `resource_id` values returned here are the ones to use in `PATCH` and `DELET +```python Python SDK +resources = client.connectors.list_resources("{connector_id}") +``` + +```typescript TypeScript SDK +const resources = await client.connectors.listResources({ id: "{connector_id}" }); +``` + ```bash cURL curl 'https://api.hydradb.com/connectors/{id}/resources' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -48,11 +56,11 @@ curl 'https://api.hydradb.com/connectors/{id}/resources' \ -`database_override` and `collection_override` are empty when the resource uses the connector's database and collection. `tenant_id_override` and `sub_tenant_id_override` repeat them under their deprecated names. +`database_override` and `collection_override` are empty when the resource uses the connector's database and collection.
-## Related Resources +## Related resources - [Update Connector Resource](/api-reference/v2/endpoint/update-connector-resource): change one resource's instructions or access rule - [Get Connector Status](/api-reference/v2/endpoint/get-connector-status): per-resource health diff --git a/api-reference/v2/endpoint/connectors-overview.mdx b/api-reference/v2/endpoint/connectors-overview.mdx index cf8a9871..55a3d022 100644 --- a/api-reference/v2/endpoint/connectors-overview.mdx +++ b/api-reference/v2/endpoint/connectors-overview.mdx @@ -1,14 +1,12 @@ --- -title: "Connectors - Overview" -description: "Every connector endpoint, the order to call them in, and which one to use to change what." +title: "Connectors: Overview" +description: "Connect your apps and manage the context they sync." --- Connectors keep data from an external app (Slack, GitHub, Google Drive, Supabase and more) synced into your database without manual ingestion. [List Connector Providers](/api-reference/v2/endpoint/list-connector-providers) returns every provider you can connect. ## Endpoints -The sidebar lists the endpoints you use to set a connector up and change it. The others are linked here. - Set up a connector: - [List Connector Providers](/api-reference/v2/endpoint/list-connector-providers): `GET /connectors/providers` lists the providers you can connect. Add `?id={provider}` to describe one, including the credentials it needs. @@ -39,15 +37,13 @@ Change it: 2. `GET /connectors/{id}/discover` to see which resources are available. 3. `POST /connectors/{id}/configure` with the resources to sync. This starts the first sync. 4. `GET /connectors/{id}/status` until `status` is `healthy` and `lifecycle` is `active`. -5. `POST /query` to search the synced data. `query_apps` defaults to `true`, so connector content is searched without setting it. +5. `POST /query` to search the database and collection chosen during configuration. For restricted content, send the caller’s identity as `acl`; omitting it leaves results unfiltered. `query_apps` is enabled by default. After that, syncs run every `sync_interval_seconds` (one hour by default). Call `POST /connectors/{id}/sync` only when you need data sooner. ## Which endpoint changes what - - To change a resource that is already configured, use `PATCH /connectors/{id}/resources/{resource_id}` or `POST /connectors/{id}/configure`. `POST /connectors/{id}/resources` adds new resources only: on an existing `resource_id` it returns `409` and changes nothing. - +To change a resource that is already configured, use `PATCH /connectors/{id}/resources/{resource_id}` or `POST /connectors/{id}/configure`. `POST /connectors/{id}/resources` adds new resources only: on an existing `resource_id` it returns `409` and changes nothing. - Instructions or access rule on one resource: `PATCH /connectors/{id}/resources/{resource_id}`. Only the fields you send change. - Instructions, sync interval or credentials for the whole connector: `PATCH /connectors/{id}`. Only the fields you send change. @@ -60,7 +56,7 @@ In paths, `{resource_id}` is the resource's id as returned by the Discover and L All connector endpoints use your HydraDB API key: -```bash +```http Authorization: Bearer $HYDRA_DB_API_KEY API-Version: 2 ``` @@ -76,15 +72,16 @@ API-Version: 2 Every object a connector syncs carries two metadata layers. -**Attributes (`metadata`)** are declared in your database's metadata schema and indexed for fast exact-match filtering. HydraDB always writes `connector_id` and `provider` here. Add your own fields per resource with `metadata` on [Configure Connector](/api-reference/v2/endpoint/configure-connector). The system fields win on conflict. +`metadata` contains fields declared in your database's metadata schema and indexed for exact-match filtering. HydraDB always writes `connector_id` and `provider` here. Add your own fields per resource with `metadata` on [Configure Connector](/api-reference/v2/endpoint/configure-connector). The system fields win on conflict. -**Custom attributes (`additional_metadata`)** are free-form and need no schema. HydraDB always writes `connector_id` and `resource_id` here, plus provider-specific fields that [List Connector Providers](/api-reference/v2/endpoint/list-connector-providers) shows under `filterable_fields` when called with `?id={provider}`. Add your own per resource with `additional_metadata` on Configure. Provider-generated fields win on conflict. +`additional_metadata` contains free-form fields and needs no schema. HydraDB always writes `connector_id` and `resource_id` here, plus provider-specific fields that [List Connector Providers](/api-reference/v2/endpoint/list-connector-providers) shows under `filterable_fields` when called with `?id={provider}`. Add your own per resource with `additional_metadata` on Configure. Provider-generated fields win on conflict. To scope a query to one connector, filter on `connector_id`: -```json Querying with a custom attribute filter +```json Querying with a metadata filter { "database": "acme_corp", + "collection": "company_docs", "query": "deployment checklist", "metadata_filters": { "additional_metadata": { diff --git a/api-reference/v2/endpoint/create-connector.mdx b/api-reference/v2/endpoint/create-connector.mdx index 6288363d..be5ec350 100644 --- a/api-reference/v2/endpoint/create-connector.mdx +++ b/api-reference/v2/endpoint/create-connector.mdx @@ -15,6 +15,30 @@ Optional settings you can also pass here, and change later with [Update Connecto +```python Python SDK +connector = client.connectors.create( + provider="slack", + name="acme-engineering", + database="acme_corp", + collection="company_docs", + provider_account_scope="T12345ACME", + credentials={"access_token": "xoxp-..."}, +) +connector_id = connector.connector_id +``` + +```typescript TypeScript SDK +const connector = await client.connectors.create({ + provider: "slack", + name: "acme-engineering", + database: "acme_corp", + collection: "company_docs", + providerAccountScope: "T12345ACME", + credentials: { access_token: "xoxp-..." }, +}); +const connectorId = connector.connectorId; +``` + ```bash cURL curl -X POST 'https://api.hydradb.com/connectors' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -24,7 +48,7 @@ curl -X POST 'https://api.hydradb.com/connectors' \ "provider": "slack", "name": "acme-engineering", "database": "acme_corp", - "collection": "engineering", + "collection": "company_docs", "provider_account_scope": "T12345ACME", "credentials": { "access_token": "xoxp-..." @@ -42,9 +66,7 @@ curl -X POST 'https://api.hydradb.com/connectors' \ "provider": "slack", "name": "acme-engineering", "database": "acme_corp", - "collection": "engineering", - "tenant_id": "acme_corp", - "sub_tenant_id": "engineering", + "collection": "company_docs", "provider_account_scope": "T12345ACME", "lifecycle": "pending_setup", "sync_status": "idle", @@ -57,20 +79,18 @@ curl -X POST 'https://api.hydradb.com/connectors' \ -The response repeats `database` and `collection` under their deprecated names `tenant_id` and `sub_tenant_id`. Read the new names. - ## Errors - `400`: `provider` or `database` is missing, the provider is unknown, or a setting such as `sync_interval_seconds` is out of range. - `402`: your plan's connector limit is reached. -- `403`: the provider is not available to your workspace. +- `403`: the provider is not available to your organization. - `422`: `credentials` does not match the provider's `credential_schema`.
-## Related Resources +## Related resources - **Next:** [Discover Resources](/api-reference/v2/endpoint/discover-connector-resources): see what the credentials can reach - **Next:** [Configure Connector](/api-reference/v2/endpoint/configure-connector): choose resources and start syncing - [List Connector Providers](/api-reference/v2/endpoint/list-connector-providers): the credential schema for a provider -- [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) +- [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 3b6c045a..6ca82be4 100644 --- a/api-reference/v2/endpoint/create-tenant.mdx +++ b/api-reference/v2/endpoint/create-tenant.mdx @@ -1,6 +1,6 @@ --- title: "Create Database" -description: "Creates a space for storing context. " +description: "Create an isolated database, with an optional metadata schema." openapi: "api-reference/v2/openapi.json POST /databases" --- @@ -16,7 +16,6 @@ response = client.databases.create( "name": "category", "data_type": "VARCHAR", "max_length": 256, - "enable_match": True, }, { "name": "product_description", @@ -37,7 +36,6 @@ const response = await client.databases.create({ name: "category", dataType: "VARCHAR", maxLength: 256, - enableMatch: true, }, { name: "product_description", @@ -61,8 +59,7 @@ curl -X POST 'https://api.hydradb.com/databases' \ { "name": "category", "data_type": "VARCHAR", - "max_length": 256, - "enable_match": true + "max_length": 256 }, { "name": "product_description", @@ -79,14 +76,10 @@ 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. - - | 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. See the [Scoping using metadata](/essentials/v2/metadata#step-1a-declare-the-schema-at-database-creation) guide for detailed schema parameters. Formerly `tenant_metadata_schema`; the `tenant_metadata_schema` alias is still accepted (deprecated). (default=`null`) | +| | Account-scoped database identifier. Use a stable ID up to 255 characters of lowercase letters, digits, `-`, and `_`; anything else, including uppercase or spaces, returns `400`. | +| | Defines database-level metadata fields. See [Metadata and Filters](/essentials/v2/metadata#schema-field-reference) for field options. | ## Successful response @@ -129,33 +122,34 @@ Always check if a database is ready before using it. Use [Database Status](/api- ## What happens after database creation? -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. -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` +1. **Wait for provisioning:** creation is asynchronous. Poll [Database Status](/api-reference/v2/endpoint/tenant-status) until `infra.ready_for_ingestion` is `true`. +2. **Collections:** Ingest with a new `collection` name to create it automatically. Omit `collection` to use the default collection, also created on first ingestion. No separate create call is needed. +3. **Retry failed databases:** If a database appears in `data.failed_databases` from [List Databases](/api-reference/v2/endpoint/list-tenants), re-create that database with `POST /databases` after addressing the reported issue. Poll status again before ingestion. +4. **Ingest:** start [ingesting context](/api-reference/v2/endpoint/ingest-context) once the database is ready. +5. **Query:** check [ingestion status](/api-reference/v2/endpoint/source-status), and start querying once sources show `graph_creation` (searchable) or `completed`. ---- ## 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, type change, or Milvus backfill for newly added dense/sparse metadata lanes. Plan your schema carefully before creating the database. - +- Existing field names and types cannot change. [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema) can add fields later. +- Dense and sparse metadata search can only be enabled at database creation, with `enable_dense_embedding` and `enable_sparse_embedding`. +- Each flag adds one vector field. A database can have at most six vector fields. Exceeding this limit or declaring an `ARRAY` field returns `400`. -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`). +See [Metadata and Filters](/essentials/v2/metadata) for field options, ingestion, and filtering examples. -For detailed parameters, valid data types, limits, shorthand flags, and comprehensive examples, see the [metadata](/essentials/v2/metadata) guide. ---- +## Errors + +Common codes: `400 INVALID_INPUT` (missing or invalid `database`, or an invalid schema), `403 FORBIDDEN` (your plan's database limit is reached), `409 DATABASE_ALREADY_EXISTS` (the `database` is already in use; the deprecated `POST /tenants` route returns `INVALID_INPUT` instead), and `500 INTERNAL_ERROR` (retry; if the message says the rollback also failed, delete the database first, then create it again). See [Error Responses](/api-reference/v2/error-responses) for the full list. +
-## **Related Resources** +## 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 -- **Read more:** [Concepts → Multi-Tenant Support](/essentials/v2/multi-tenant) -- **Read more:** [Usage → Metadata](/essentials/v2/metadata) +- **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:** [Multi-tenancy](/essentials/v2/multi-tenant) +- **Read more:** [Metadata and Filters](/essentials/v2/metadata) diff --git a/api-reference/v2/endpoint/delete-collection.mdx b/api-reference/v2/endpoint/delete-collection.mdx index 9850a8fd..55561abb 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. | +| | Identifier of the database that owns the collection. | +| | Identifier of the collection to delete. | @@ -80,30 +80,26 @@ Deletion is asynchronous: a `200` means cleanup was accepted, not that it finish There is no collection-level completion endpoint, and a repeated `DELETE` returning `200` is not a completion signal either. -You do not need one to reuse the name safely. Ingestion creates a missing collection implicitly, so a successful write is not evidence that cleanup finished; what makes it safe is the fence. While cleanup is running the API rejects the write with `404`, and the fence is lifted only once every store has been purged. So write when you are ready: a `404` means cleanup is still going and you should try again later, and a success means it was already done. Do not treat a successful write as a *probe*, because it also creates the collection. +To reuse the collection name: -## Behavior notes +- Ingest the new context. While cleanup is running, the API returns `404`. Retry later. +- Writes succeed only after cleanup finishes in every store. A successful write also creates the collection, so use it to ingest context rather than to check deletion status. - -**Irreversible action.** Ingested documents, memories, 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. - +## Behavior notes -- **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. +- In-flight ingestion cancelled by deletion is reported as failed. - **Dashboard:** Owners can also expand a database on the Databases page and delete a collection from the inline list. ## 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` (`database` or `collection` is missing), `404 DATABASE_NOT_FOUND` (a database that is itself being deleted returns `404 NOT_FOUND`), `401 UNAUTHORIZED`, `503 SERVICE_UNAVAILABLE` (an earlier failed deletion of this collection is still releasing its lock; the collection stays fenced, so retry in a few seconds), and `500 INTERNAL_ERROR` (transient; retry the same `DELETE`). See [Error Responses](/api-reference/v2/error-responses) for the full list.
- -**Related Resources** +## 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 knowledge or memories 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/multi-tenant) - +- **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 knowledge or memories 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/multi-tenant) diff --git a/api-reference/v2/endpoint/delete-connector-resource.mdx b/api-reference/v2/endpoint/delete-connector-resource.mdx index 9ea23415..5b12a920 100644 --- a/api-reference/v2/endpoint/delete-connector-resource.mdx +++ b/api-reference/v2/endpoint/delete-connector-resource.mdx @@ -10,6 +10,14 @@ Removes a resource from the connector so it is no longer synced. Objects already +```python Python SDK +client.connectors.delete_resource("{connector_id}", "{resource_id}") +``` + +```typescript TypeScript SDK +await client.connectors.deleteResource({ id: "{connector_id}", resourceId: "{resource_id}" }); +``` + ```bash cURL curl -X DELETE 'https://api.hydradb.com/connectors/{id}/resources/{resource_id}' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -32,7 +40,7 @@ curl -X DELETE 'https://api.hydradb.com/connectors/{id}/resources/{resource_id}'
-## Related Resources +## Related resources - [List Connector Resources](/api-reference/v2/endpoint/connector-resources): check the resource is gone - [Configure Connector](/api-reference/v2/endpoint/configure-connector): add it back diff --git a/api-reference/v2/endpoint/delete-connector.mdx b/api-reference/v2/endpoint/delete-connector.mdx index f056b5f8..71854366 100644 --- a/api-reference/v2/endpoint/delete-connector.mdx +++ b/api-reference/v2/endpoint/delete-connector.mdx @@ -10,6 +10,14 @@ To stop syncing for a while without losing the configuration, [pause](/api-refer +```python Python SDK +client.connectors.delete("{connector_id}") +``` + +```typescript TypeScript SDK +await client.connectors.delete({ id: "{connector_id}" }); +``` + ```bash cURL curl -X DELETE 'https://api.hydradb.com/connectors/{id}' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -31,7 +39,7 @@ curl -X DELETE 'https://api.hydradb.com/connectors/{id}' \
-## Related Resources +## Related resources -- [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) +- [Connectors: Overview](/api-reference/v2/endpoint/connectors-overview) - [List Connectors](/api-reference/v2/endpoint/list-connectors): check the connector is gone diff --git a/api-reference/v2/endpoint/delete-source.mdx b/api-reference/v2/endpoint/delete-source.mdx index 9f7827a8..a56a4c64 100644 --- a/api-reference/v2/endpoint/delete-source.mdx +++ b/api-reference/v2/endpoint/delete-source.mdx @@ -8,14 +8,13 @@ import { Field } from "/snippets/field.jsx"; Specify the resource category with the `type` parameter: -- `type=knowledge` _(default)_ - delete knowledge sources. -- `type=memory` - delete memories. +- `type=knowledge` _(default)_: delete knowledge sources. +- `type=memory`: delete memories. +- `type=all`: delete matching IDs from both. Pass one or more IDs in `ids`. Send `database`, `collection`, `ids`, and `type` as top-level fields in the request body. -### Knowledge deletion - -Use `type: "knowledge"` and pass knowledge `ids` in the `ids` array. Include the same `collection` you used when ingesting the knowledge; omitting it targets the default collection. +Include the same `collection` used at ingestion. Omitting it targets the default collection. @@ -52,14 +51,18 @@ curl -X DELETE 'https://api.hydradb.com/context' \ +## Response + +Check `data.deleted_count` for the number deleted and `data.results[]` for each ID's outcome. + -```json Success +```json Success knowledge { "success": true, "data": { "success": true, - "message": "Delete completed", + "message": "Successfully deleted 2 source(s)", "results": [ { "id": "policy_main", "deleted": true }, { "id": "runbook_deploy", "deleted": true } @@ -74,50 +77,7 @@ curl -X DELETE 'https://api.hydradb.com/context' \ } ``` - - -### Memory deletion - -Use `type: "memory"` and pass memory `ids` in the `ids` array. Include the same `collection` you used when ingesting the memories; omitting it targets the default collection. - - - -```python Python SDK -client.context.delete( - type="memory", - database="acme_corp", - collection="user_alex", - ids=["mem_user_alex_tone"], -) -``` - -```typescript TypeScript SDK -await client.context.delete({ - type: "memory", - database: "acme_corp", - collection: "user_alex", - ids: ["mem_user_alex_tone"], -}); -``` - -```bash cURL -curl -X DELETE 'https://api.hydradb.com/context' \ - -H "Authorization: Bearer " \ - -H "API-Version: 2" \ - -H "Content-Type: application/json" \ - -d '{ - "database": "acme_corp", - "collection": "user_alex", - "ids": ["mem_user_alex_tone"], - "type": "memory" - }' -``` - - - - - -```json Success +```json Success memory { "success": true, "data": { @@ -137,152 +97,32 @@ curl -X DELETE 'https://api.hydradb.com/context' \ } ``` -```json 404 - nothing matched (strict mode) -{ - "success": false, - "data": { - "success": false, - "message": "No sources were deleted", - "results": [ - { - "id": "mem_user_alex_tone", - "deleted": false, - "error": "Memory not found or deletion failed" - } - ], - "deleted_count": 0, - "user_memory_deleted": 0 - }, - "error": { - "code": "NOT_FOUND", - "message": "No sources were deleted" - }, - "meta": { - "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", - "latency_ms": 4.8 - } -} -``` - ## Status codes -**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. - -```bash Default - always 200 -curl -X DELETE 'https://api.hydradb.com/context' \ - -H "Authorization: Bearer " \ - -H "API-Version: 2" \ - -H "Content-Type: application/json" \ - -d '{ "database": "acme_corp", "ids": ["policy_main"], "type": "knowledge" }' -``` - -### Opt in to honest status codes +The default mode can return `200` even when nothing was deleted. Check the response body to confirm deletion. -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 -code alone. + -```bash Strict - honest status codes -curl -X DELETE 'https://api.hydradb.com/context' \ - -H "Authorization: Bearer " \ - -H "API-Version: 2" \ - -H "X-HydraDB-Delete-Status: strict" \ - -H "Content-Type: application/json" \ - -d '{ "database": "acme_corp", "ids": ["policy_main"], "type": "knowledge" }' -``` +### Strict status codes -In strict mode: +Send `X-HydraDB-Delete-Status: strict` to receive error statuses when deletion fails. | Status | Code | Meaning | | --- | --- | --- | -| `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. | -| `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 - 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. - - -On `404`, `409`, and `500` the response `data` still carries the same -`results` / `deleted_count` payload a `200` carries, so you can read per-ID -outcomes on a failure exactly as you would on success: - -```json 409 - still indexing (strict mode) -{ - "success": false, - "data": { - "success": false, - "message": "Source is still processing; retry deletion after ingestion completes", - "results": [ - { - "id": "policy_main", - "deleted": false, - "error": "Source is still processing; retry deletion after ingestion completes" - } - ], - "deleted_count": 0 - }, - "error": { - "code": "SOURCE_PROCESSING", - "message": "Source is still processing; retry deletion after ingestion completes" - }, - "meta": { - "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", - "latency_ms": 8.1 - } -} -``` - -### Which mode you get - -The header always wins. Without it, the server default applies. - -| Request | Behaviour | -| --- | --- | -| 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. | - - - **The default is expected to become `strict` in a future release.** Adopting - `strict` now means that change is a no-op for you. - - 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. - - If your integration checks `response.ok` or `status == 200` today, it is - treating blocked deletes as successful. That is the failure strict mode - surfaces. - - -## Some additional notes +| `200` | None | At least one item was deleted. Check `results[]` for each ID. | +| `404` | `NOT_FOUND` | Nothing matched the supplied IDs. | +| `409` | `SOURCE_PROCESSING` | A source is still indexing. Retry after processing completes. | +| `500` | `INTERNAL_ERROR` | Deletion failed. Retry the request. | -- **Partial-success semantics:** For `type=knowledge`, each ID is reported independently in `results[]`. A failure on one ID does not stop the rest. For `type=memory`, the response reports an aggregate `user_memory_deleted` reflecting _all_ listed IDs. One exception: if any source in the request is still indexing, the whole request is refused and nothing is deleted - reported as `409` in strict mode, and as a `200` with `deleted_count: 0` by default. -- **Retrieval drops the source immediately:** Even before background cleanup finishes, deleted IDs disappear from `/query` and `/context/list` responses. -- **Mixed deletes need two calls:** To delete both knowledge and memory items, send two requests - one with `type=knowledge`, one with `type=memory`. +A knowledge batch is not deleted while any requested source is still indexing. Check [Ingestion Status](/api-reference/v2/endpoint/source-status) before retrying.
- - **Related Resources** +## Related resources - - **Find IDs:** [List Documents](/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 - - **Read more:** [Context Management - Overview](/api-reference/v2/endpoint/sources-overview) - +- **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) +- **Delete everything:** [Delete Database](/api-reference/v2/endpoint/delete-tenant), which 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 4fa82d43..c44b9013 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 ingested content, memories, metadata schema, vector indices, and graphs. 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 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. @@ -31,7 +31,7 @@ curl -X DELETE 'https://api.hydradb.com/databases?database=database_to_delete' \ ## Query parameters | Name | Description | | --- | --- | -| | Identifier of the database to delete. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | +| | Identifier of the database to delete. | @@ -70,31 +70,24 @@ curl -X DELETE 'https://api.hydradb.com/databases?database=database_to_delete' \ ## Deletion completion -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`. +Deletion is asynchronous: the endpoint returns once the database is deregistered, and cleanup of vector stores, graphs, and storage objects runs in the background for a few minutes. 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. ## Behavior notes - -**Irreversible action.** Ingested documents, memories, embeddings, graph nodes, metadata schema, and storage objects are permanently removed. There is no recovery window - 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. -- **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 an already-deleted database returns `404 DATABASE_NOT_FOUND`. Deleting a database that is still deleting returns `200` again. Deleting a database that is still provisioning, or whose provisioning or earlier deletion failed, is treated as a request to tear down that database. ## 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: `400 INVALID_INPUT` (missing `database`), `401 UNAUTHORIZED`, `404 DATABASE_NOT_FOUND`, and `500 INTERNAL_ERROR` (transient; retry the same `DELETE`). See [Error Responses](/api-reference/v2/error-responses) for the full list.
- -**Related Resources** +## 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 -- **Alternative:** [Delete Context](/api-reference/v2/endpoint/delete-source) - remove specific knowledge or memories without deleting the whole database -- **Read more:** [Concepts → Multi-Tenant Support](/essentials/v2/multi-tenant) - +- **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 knowledge or memories without deleting the whole database +- **Read more:** [Multi-tenancy](/essentials/v2/multi-tenant) diff --git a/api-reference/v2/endpoint/delete-webhook.mdx b/api-reference/v2/endpoint/delete-webhook.mdx index 2efcfae9..30db0c6c 100644 --- a/api-reference/v2/endpoint/delete-webhook.mdx +++ b/api-reference/v2/endpoint/delete-webhook.mdx @@ -1,9 +1,27 @@ --- title: "Delete Webhook" -description: "Remove the webhook registration for your workspace." +description: "Remove the webhook registration for your organization." openapi: "api-reference/v2/openapi.json DELETE /webhooks/indexing" --- Removes the webhook registration. HydraDB stops sending deliveries, and the stored signing secret is discarded along with the registration. To stop signing deliveries while keeping the webhook itself, call `DELETE /webhooks/indexing/signing-secret` instead. See [Manage the signing secret](/essentials/v2/webhooks#manage-the-signing-secret). + + + +```python Python SDK +client.webhooks.delete() +``` + +```typescript TypeScript SDK +await client.webhooks.delete(); +``` + +```bash cURL +curl -X DELETE 'https://api.hydradb.com/webhooks/indexing' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + diff --git a/api-reference/v2/endpoint/disable-webhook-signing.mdx b/api-reference/v2/endpoint/disable-webhook-signing.mdx new file mode 100644 index 00000000..b291b55b --- /dev/null +++ b/api-reference/v2/endpoint/disable-webhook-signing.mdx @@ -0,0 +1,36 @@ +--- +title: "Disable Signing" +description: "Remove the signing secret while keeping the webhook registered." +openapi: "api-reference/v2/openapi.json DELETE /webhooks/indexing/signing-secret" +--- + +Removes the stored secret. The webhook stays registered and continues receiving deliveries, but new deliveries no longer carry `X-HydraDB-Signature`. If no webhook is registered, this endpoint returns `404`. + + +Without signing, your receiver cannot use the HydraDB signature to verify a delivery. Update your receiver's verification policy before disabling signing. + + + + +```python Python SDK +client.webhooks.clear_signing_secret() +``` + +```typescript TypeScript SDK +await client.webhooks.clearSigningSecret(); +``` + +```bash cURL +curl -X DELETE 'https://api.hydradb.com/webhooks/indexing/signing-secret' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + + +Editing the webhook registration preserves its signing secret. Use this endpoint to disable signing, or [Delete Webhook](/api-reference/v2/endpoint/delete-webhook) to stop deliveries entirely. + +## Related resources + +- [Set Signing Secret](/api-reference/v2/endpoint/set-webhook-signing-secret): enable signing again +- [Get Webhook](/api-reference/v2/endpoint/get-webhook): read the signing status diff --git a/api-reference/v2/endpoint/discover-connector-resources.mdx b/api-reference/v2/endpoint/discover-connector-resources.mdx index ab259934..a443360d 100644 --- a/api-reference/v2/endpoint/discover-connector-resources.mdx +++ b/api-reference/v2/endpoint/discover-connector-resources.mdx @@ -14,6 +14,19 @@ For providers with many resources, page through the list with `limit` (1 to 100) +```python Python SDK +found = client.connectors.discover("{connector_id}") +for resource in found.resources: + print(resource.id, resource.name, resource.resource_type) +``` + +```typescript TypeScript SDK +const found = await client.connectors.discover({ id: "{connector_id}" }); +for (const resource of found.resources ?? []) { + console.log(resource.id, resource.name, resource.resourceType); +} +``` + ```bash cURL curl 'https://api.hydradb.com/connectors/{id}/discover' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -46,13 +59,13 @@ curl 'https://api.hydradb.com/connectors/{id}/discover' \ ## Errors -- `404`: no connector with this id in your workspace. +- `404`: no connector with this id in your organization. - `422`: the stored credentials are no longer valid. Replace them with [Update Connector](/api-reference/v2/endpoint/update-connector). - `400` or `502`: the provider rejected the request or could not be reached. The message carries the provider's explanation.
-## Related Resources +## Related resources - **Next:** [Configure Connector](/api-reference/v2/endpoint/configure-connector): choose resources and start syncing - [List Connector Resources](/api-reference/v2/endpoint/connector-resources): resources already configured diff --git a/api-reference/v2/endpoint/fetch-content.mdx b/api-reference/v2/endpoint/fetch-content.mdx index 8dcfa551..8ae108a8 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 content of a knowledge or memory source." +description: "Read the original content of a knowledge source or memory, or get a download URL." openapi: "api-reference/v2/openapi.json GET /context/inspect" --- import { Field } from "/snippets/field.jsx"; -Specify the `id` of the knowledge or memory you want to retrieve. +Returns the original content of one knowledge source or memory, a presigned download URL, or both. Pass its `id`. @@ -42,28 +42,24 @@ curl -G 'https://api.hydradb.com/context/inspect' \ ## Query parameters - | Name | Description | | --- | --- | | | ID of the source 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`) | +| | Owning database. | +| | Collection scope. If omitted, the default collection is used. | | | 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`) | +| | TTL of the presigned URL (when `mode` includes `url`). Range `60` to `604800` (7 days). (default=`3600`) | +| | Principals to answer as (document ACLs). A source they cannot see returns the same `404` as a missing one. Repeated (`acl=a&acl=b`) or comma-separated. Omit for no ACL scoping. See [Access Control](/essentials/v2/access-control). | ## Fetch modes | Mode | Returns | Use when | | --- | --- | --- | -| `content` | The payload in `content_base64` (base64-encoded); `content` is `null` in this mode. Decode `content_base64` to recover the parsed text or the raw binary bytes. | You want to render the content in-app or feed it to another model. | -| `url` | Presigned URL (`presigned_url`) valid for `expiry_seconds`. | You want a client or a service to download the original file directly without proxying through your backend. | -| `both`_(default)_ | Text **and** presigned URL. | UI flows that show parsed text inline plus a "Download original" link. | - -Regardless of `mode`, the response also includes `inferred_content` when available - the model-derived text for the source (for memories, the inferred memory statement; for knowledge, derived/normalized content). It is `null` when the source has no inferred content. +| `content` | Original text in `content`, or binary data encoded as base64 in `content_base64`. The unused field is `null`. | You want the content inline. It is the stored original, so a PDF comes back as base64, not extracted text. Check both fields for unknown types. | +| `url` | Presigned URL (`presigned_url`) valid for `expiry_seconds`. `content`, `content_base64`, and `inferred_content` are `null`. | You want a client or a service to download the original file directly without proxying through your backend. | +| `both`_(default)_ | Everything `content` returns, **and** presigned URL. | UI flows that show text inline plus a "Download original" link. | - -Use `mode=url` when you need the original file. Use `mode=content` when you only need extracted text for display, summarization, or prompting. - +In `content` and `both` modes, the response also includes `inferred_content` when available: the model-derived text for the source (for memories, the inferred memory statement; for knowledge, derived/normalized content). It is `null` when the source has no inferred content. ### Mode examples @@ -76,12 +72,13 @@ Use `mode=url` when you need the original file. Use `mode=content` when you only "success": true, "id": "policy_main", "content": null, - "content_base64": "U2VjdGlvbiAxLiBBdXRoZW50aWNhdGlvbiBwb2xpY2llcy4uLg==", + "content_base64": "JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2c...", "inferred_content": null, "presigned_url": null, "content_type": "application/pdf", "size_bytes": 1842233, - "message": "File fetched successfully" + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -104,7 +101,8 @@ Use `mode=url` when you need the original file. Use `mode=content` when you only "presigned_url": "https://storage.hydradb.com/.../policy_main.pdf?X-Amz-...", "content_type": "application/pdf", "size_bytes": 1842233, - "message": "File fetched successfully" + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -127,7 +125,8 @@ Use `mode=url` when you need the original file. Use `mode=content` when you only "presigned_url": "https://storage.hydradb.com/.../diagram.png?X-Amz-...", "content_type": "image/png", "size_bytes": 92844, - "message": "File fetched successfully" + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -137,7 +136,7 @@ Use `mode=url` when you need the original file. Use `mode=content` when you only } ``` - + ```json { "success": true, @@ -147,10 +146,11 @@ Use `mode=url` when you need the original file. Use `mode=content` when you only "content": "Prefers concise answers and dark mode.", "content_base64": null, "inferred_content": "User prefers concise answers and dark mode.", - "presigned_url": null, - "content_type": "text/plain", - "size_bytes": null, - "message": "Memory fetched successfully" + "presigned_url": "https://storage.hydradb.com/.../mem_user_alex_tone?X-Amz-...", + "content_type": "text/plain; charset=utf-8", + "size_bytes": 38, + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -170,13 +170,14 @@ Use `mode=url` when you need the original file. Use `mode=content` when you only "data": { "success": true, "id": "policy_main", - "content": "Section 1. Authentication policies...", - "content_base64": null, + "content": null, + "content_base64": "JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2c...", "inferred_content": null, "presigned_url": "https://storage.hydradb.com/.../policy_main.pdf?X-Amz-...", "content_type": "application/pdf", "size_bytes": 1842233, - "message": "File fetched successfully" + "message": "File fetched successfully", + "error": null }, "error": null, "meta": { @@ -191,8 +192,8 @@ Use `mode=url` when you need the original file. Use `mode=content` when you only "success": false, "data": null, "error": { - "code": "SOURCE_NOT_FOUND", - "message": "Source not found" + "code": "NOT_FOUND", + "message": "Source 'policy_main' not found. Verify the id is correct and the source has been ingested." }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -203,38 +204,17 @@ Use `mode=url` when you need the original file. Use `mode=content` when you only -```json For a binary file -{ - "success": true, - "data": { - "success": true, - "id": "diagram_png", - "content": null, - "content_base64": "iVBORw0KGgoAAAANSUhEUgAA...", - "inferred_content": null, - "presigned_url": "https://storage.hydradb.com/.../diagram.png?X-Amz-...", - "content_type": "image/png", - "size_bytes": 92844, - "message": "File fetched successfully" - }, - "error": null, - "meta": { - "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", - "latency_ms": 12.3 - } -} -``` - ## Behavior notes - - **Text vs binary handling.** In `mode=both`, text-parseable sources (PDF, DOCX, MD, TXT) populate `content` with parsed text while non-text/binary files come back in `content_base64`; check both fields when handling unknown content types. In `mode=content`, the payload is always returned base64-encoded in `content_base64` and `content` is `null` - decode `content_base64` to recover the text or bytes. - +- **Recently ingested sources:** Fetching immediately after ingestion returns the stored original, but `inferred_content` stays `null` until processing produces it. 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 file during that window, so treat it as a short-lived secret. +- **Memory items:** Fetching a memory's `id` returns its raw text content (a conversation memory returns its stored JSON, with `user_name` and `pairs`). Memories are stored like files, so `mode=url` and `mode=both` also return a `presigned_url`. -- **`inferred_content`:** Alongside the raw `content`, the response carries `inferred_content` - the model-derived text for the source. For **memories** this is the inferred memory statement (e.g. `"User prefers concise answers and dark mode."`); for **knowledge** sources it is typically `null` unless derived content exists. It is returned for every `mode`. +## Errors -- **Recently ingested sources:** Fetching immediately after ingestion may return a record before the parsed text 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 file during that window, so treat it as a short-lived secret. -- **Memory items:** Fetching a memory's `id` returns its raw text content. There are no presigned URLs for memory items - `mode=url` and `mode=both` return `presigned_url: null`. +Common codes: `400 INVALID_INPUT` (missing `id` or `database`, an unknown `mode`, or `expiry_seconds` outside `60` to `604800`), `404 DATABASE_NOT_FOUND`, `404 NOT_FOUND` (the source does not exist, or `acl` is set and its principals may not see it), `500 INTERNAL_ERROR` (a transient storage error; retry). See [Error Responses](/api-reference/v2/error-responses) for the full list. + +## Related resources -
+- [List Context](/api-reference/v2/endpoint/list-documents): find a source ID +- [Ingestion Status](/api-reference/v2/endpoint/source-status): check processing progress diff --git a/api-reference/v2/endpoint/get-connector-status.mdx b/api-reference/v2/endpoint/get-connector-status.mdx index af1fd2d0..54313435 100644 --- a/api-reference/v2/endpoint/get-connector-status.mdx +++ b/api-reference/v2/endpoint/get-connector-status.mdx @@ -8,6 +8,16 @@ Answers "is this connector working?" in one call. It returns an overall `status` +```python Python SDK +status = client.connectors.status("{connector_id}") +print(status.status, status.lifecycle) +``` + +```typescript TypeScript SDK +const status = await client.connectors.status({ id: "{connector_id}" }); +console.log(status.status, status.lifecycle); +``` + ```bash cURL curl 'https://api.hydradb.com/connectors/{id}/status' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -77,7 +87,7 @@ For each resource, `status` is the result of its last sync: `ok`, `empty` (ran a
-## Related Resources +## Related resources - [List Connector Resources](/api-reference/v2/endpoint/connector-resources): each resource's configuration and sync cursor - [Sync Connector](/api-reference/v2/endpoint/sync-connector): start a sync after fixing a failure diff --git a/api-reference/v2/endpoint/get-connector.mdx b/api-reference/v2/endpoint/get-connector.mdx index 20f107cf..79f6b8e9 100644 --- a/api-reference/v2/endpoint/get-connector.mdx +++ b/api-reference/v2/endpoint/get-connector.mdx @@ -10,6 +10,14 @@ Returns one connector: where it syncs to, its connector-level `custom_instructio +```python Python SDK +connector = client.connectors.get("{connector_id}") +``` + +```typescript TypeScript SDK +const connector = await client.connectors.get({ id: "{connector_id}" }); +``` + ```bash cURL curl 'https://api.hydradb.com/connectors/{id}' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -45,11 +53,11 @@ curl 'https://api.hydradb.com/connectors/{id}' \ ## Errors -- `404`: no connector with this id in your workspace. +- `404`: no connector with this id in your organization.
-## Related Resources +## Related resources - [Update Connector](/api-reference/v2/endpoint/update-connector): change these settings - [List Connector Resources](/api-reference/v2/endpoint/connector-resources): the connector's resources diff --git a/api-reference/v2/endpoint/get-metadata-schema.mdx b/api-reference/v2/endpoint/get-metadata-schema.mdx new file mode 100644 index 00000000..269549ce --- /dev/null +++ b/api-reference/v2/endpoint/get-metadata-schema.mdx @@ -0,0 +1,32 @@ +--- +title: "Get Metadata Schema" +description: "Read the metadata fields declared for a database." +openapi: "api-reference/v2/openapi.json GET /databases/{database}/metadata-schema" +--- + +Returns the database's declared metadata fields in `data.fields`, including each field's type and search settings. Use it to check which fields you can send in `metadata` before ingesting or updating context. + + + +```python Python SDK +result = client.databases.get_metadata_schema(database="acme_corp") +``` + +```typescript TypeScript SDK +const result = await client.databases.getMetadataSchema({ database: "acme_corp" }); +``` + +```bash cURL +curl 'https://api.hydradb.com/databases/acme_corp/metadata-schema' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + + +The field definitions have the same shape accepted by [Create Database](/api-reference/v2/endpoint/create-tenant) and [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema). Free-form `additional_metadata` fields are not part of this schema. + +## Related resources + +- [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema): add fields +- [Metadata guide](/essentials/v2/metadata): declare and filter metadata diff --git a/api-reference/v2/endpoint/get-webhook-delivery.mdx b/api-reference/v2/endpoint/get-webhook-delivery.mdx index 16f39248..cd854363 100644 --- a/api-reference/v2/endpoint/get-webhook-delivery.mdx +++ b/api-reference/v2/endpoint/get-webhook-delivery.mdx @@ -7,3 +7,21 @@ openapi: "api-reference/v2/openapi.json GET /webhooks/indexing/deliveries/{deliv Returns one delivery record, including its current status, attempt count, and the last error code and message when an attempt failed. The `delivery_id` is the value sent in the `X-HydraDB-Delivery-ID` header of the delivery itself, so you can look up any request your endpoint received. See [Webhooks](/essentials/v2/webhooks). + + + +```python Python SDK +delivery = client.webhooks.get_delivery("{delivery_id}") +``` + +```typescript TypeScript SDK +const delivery = await client.webhooks.getDelivery({ deliveryId: "{delivery_id}" }); +``` + +```bash cURL +curl 'https://api.hydradb.com/webhooks/indexing/deliveries/{delivery_id}' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + diff --git a/api-reference/v2/endpoint/get-webhook.mdx b/api-reference/v2/endpoint/get-webhook.mdx index 2e5cb94b..ecadc022 100644 --- a/api-reference/v2/endpoint/get-webhook.mdx +++ b/api-reference/v2/endpoint/get-webhook.mdx @@ -1,13 +1,29 @@ --- title: "Get Webhook" -description: "Retrieve the current webhook registration for your workspace." +description: "Retrieve the current webhook registration for your organization." openapi: "api-reference/v2/openapi.json GET /webhooks/indexing" --- Returns the registered endpoint, the subscribed event types, and whether a signing secret is configured. - - The signing secret itself is never returned. `signing_secret_configured` tells you only whether one is set. If you have lost your secret, rotate it with `POST /webhooks/indexing/signing-secret` rather than trying to read it back. - +The signing secret itself is never returned. `signing_secret_configured` tells you only whether one is set. If you have lost your secret, rotate it with `POST /webhooks/indexing/signing-secret` rather than trying to read it back. When no webhook is registered, `registered` is `false` and `url` is `null`. See [Webhooks](/essentials/v2/webhooks). + + + +```python Python SDK +webhook = client.webhooks.get() +``` + +```typescript TypeScript SDK +const webhook = await client.webhooks.get(); +``` + +```bash cURL +curl 'https://api.hydradb.com/webhooks/indexing' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + diff --git a/api-reference/v2/endpoint/ingest-context.mdx b/api-reference/v2/endpoint/ingest-context.mdx index f92af9f8..78bbb38f 100644 --- a/api-reference/v2/endpoint/ingest-context.mdx +++ b/api-reference/v2/endpoint/ingest-context.mdx @@ -1,219 +1,52 @@ --- title: "Ingest Context" -description: "Ingestion endpoint for knowledge (documents, app sources) and user memories." +description: "Store documents, app sources, and memories for your AI." openapi: "api-reference/v2/openapi.json POST /context/ingest" --- import { Field } from "/snippets/field.jsx"; -When context is of `type=knowledge`: +Use `type: "knowledge"` for: -1. **Documents** - Use the `documents` field for binary uploads that HydraDB should parse: PDFs, Office and iWork files, spreadsheets, images and plain text. See [Supported file formats](#supported-file-formats) for the full list. -2. **App Sources** - Use `app_knowledge` for pre-extracted JSON content (Slack threads, Notion pages, emails, tickets). Read more about [ingesting knowledge from your apps](/essentials/v2/app-sources). +1. **Documents:** Use the `documents` field for binary uploads that HydraDB should parse: PDFs, Office and iWork files, spreadsheets, images and plain text. See [Supported file formats](#supported-file-formats) for the full list. +2. **App sources:** Use `app_knowledge` for text and metadata your app has already extracted (Slack threads, Notion pages, emails, tickets). Read more about [ingesting knowledge from your apps](/essentials/v2/app-sources). -When context is of `type=memory` - -Use `memories` for per-user content, scoped with `collection`. Set `infer: true` to let HydraDB extract preferences from raw signals, or `infer: false` to store the text verbatim. Read more about [ingesting memories](/essentials/v2/memories). - - -`database` and `collection` are the current field names (formerly `tenant_id` and `sub_tenant_id`). The old names remain accepted as deprecated aliases for full backward compatibility. - +Use `type: "memory"` and `memories` for preferences, decisions, inferred behavior traits, and past conversations your agent should remember in future sessions. With `infer: true`, HydraDB uses earlier context to extract preferences and improve accuracy; `infer: false` stores the text verbatim. See [Memories](/essentials/v2/memories). ```python Python SDK import json -with open("/path/to/policy.pdf", "rb") as policy: - knowledge_result = client.context.ingest( - # Knowledge requests can include documents, app_knowledge, or both. - type="knowledge", +with open("policy.pdf", "rb") as policy: + result = client.context.ingest( database="acme_corp", - collection="team_docs", - upsert=True, - - # Use documents when HydraDB should parse PDFs, DOCX, CSV, Markdown, or text files. + collection="company_docs", + type="knowledge", documents=[("policy.pdf", policy, "application/pdf")], - document_metadata=json.dumps([ - { - "id": "policy_main", - "metadata": {"department": "legal"}, - "additional_metadata": {"source": "policy"}, - } - ]), - - # Use app_knowledge when your app already extracted the source text/metadata. - app_knowledge=json.dumps([ - { - "id": "slack_thread_001", - "database": "acme_corp", - "collection": "team_docs", - "title": "Pricing discussion", - "type": "slack", - "content": {"text": "We agreed on three tiers..."}, - "metadata": {"department": "product"}, - "additional_metadata": {"channel": "pricing"}, - } - ]), + document_metadata=json.dumps([{"id": "policy_main"}]), ) - -text_memory_result = client.context.ingest( - type="memory", - database="acme_corp", - collection="user_alex", - memories=json.dumps([ - { - # Use text for raw notes or signals. - "text": "Prefers concise answers and dark mode.", - "infer": True, - "user_name": "Alex", - } - ]), -) - -conversation_memory_result = client.context.ingest( - type="memory", - database="acme_corp", - collection="user_alex", - memories=json.dumps([ - { - # Use user_assistant_pairs for conversation history instead of text. - "title": "Support conversation about refunds", - "user_assistant_pairs": [ - {"user": "Can I get a refund?", "assistant": "Refunds are available within 30 days."}, - ], - "infer": False, - } - ]), -) ``` ```typescript TypeScript SDK -const knowledgeResult = await client.context.ingest({ - // Knowledge requests can include documents, app_knowledge, or both. - type: "knowledge", +const result = await client.context.ingest({ database: "acme_corp", - collection: "team_docs", - upsert: true, - - // Use documents when HydraDB should parse PDFs, DOCX, CSV, Markdown, or text files. - documents: [ - { path: "/path/to/policy.pdf", filename: "policy.pdf", contentType: "application/pdf" }, - ], - documentMetadata: JSON.stringify([ - { - id: "policy_main", - metadata: { department: "legal" }, - additional_metadata: { source: "policy" }, - }, - ]), - - // Use app_knowledge when your app already extracted the source text/metadata. - appKnowledge: JSON.stringify([ - { - id: "slack_thread_001", - database: "acme_corp", - collection: "team_docs", - title: "Pricing discussion", - type: "slack", - content: { text: "We agreed on three tiers..." }, - metadata: { department: "product" }, - additional_metadata: { channel: "pricing" }, - }, - ]), -}); - -const textMemoryResult = await client.context.ingest({ - type: "memory", - database: "acme_corp", - collection: "user_alex", - memories: JSON.stringify([ - { - // Use text for raw notes or signals. - text: "Prefers concise answers and dark mode.", - infer: true, - user_name: "Alex", - }, - ]), -}); - -const conversationMemoryResult = await client.context.ingest({ - type: "memory", - database: "acme_corp", - collection: "user_alex", - memories: JSON.stringify([ - { - // Use user_assistant_pairs for conversation history instead of text. - title: "Support conversation about refunds", - user_assistant_pairs: [ - { user: "Can I get a refund?", assistant: "Refunds are available within 30 days." }, - ], - infer: false, - }, - ]), + collection: "company_docs", + type: "knowledge", + documents: [{ path: "policy.pdf", filename: "policy.pdf", contentType: "application/pdf" }], + documentMetadata: JSON.stringify([{ id: "policy_main" }]), }); ``` ```bash cURL curl -X POST 'https://api.hydradb.com/context/ingest' \ - -H "Authorization: Bearer " \ - -H "API-Version: 2" \ - -F "type=knowledge" \ - -F "database=acme_corp" \ - -F "collection=team_docs" \ - -F "upsert=true" \ - -F "documents=@/path/to/policy.pdf" \ - -F 'document_metadata=[ - { - "id": "policy_main", - "metadata": { "department": "legal" }, - "additional_metadata": { "source": "policy" } - } - ]' \ - -F 'app_knowledge=[ - { - "id": "slack_thread_001", - "database": "acme_corp", - "collection": "team_docs", - "title": "Pricing discussion", - "type": "slack", - "content": { "text": "We agreed on three tiers..." }, - "metadata": { "department": "product" }, - "additional_metadata": { "channel": "pricing" } - } - ]' - -# OR: memory from raw text. -curl -X POST 'https://api.hydradb.com/context/ingest' \ - -H "Authorization: Bearer " \ - -H "API-Version: 2" \ - -F "type=memory" \ - -F "database=acme_corp" \ - -F "collection=user_alex" \ - -F 'memories=[ - { - "text": "Prefers concise answers and dark mode.", - "infer": true, - "user_name": "Alex" - } - ]' - -# OR: memory from conversation pairs. -curl -X POST 'https://api.hydradb.com/context/ingest' \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ -H "API-Version: 2" \ - -F "type=memory" \ -F "database=acme_corp" \ - -F "collection=user_alex" \ - -F 'memories=[ - { - "title": "Support conversation about refunds", - "user_assistant_pairs": [ - { "user": "Can I get a refund?", "assistant": "Refunds are available within 30 days." } - ], - "infer": false - } - ]' + -F "collection=company_docs" \ + -F "type=knowledge" \ + -F "documents=@policy.pdf" \ + -F 'document_metadata=[{"id":"policy_main"}]' ``` @@ -271,66 +104,46 @@ const response = await client.context.ingest({ }); ``` -```python API -import io -import json -import requests - -text = """Q4 planning notes - -- Launch checklist is owned by Priya. -- Legal review is due by Friday. -""" - -# Create a file-like object in memory. No local .txt file is required. -txt_file = io.BytesIO(text.encode("utf-8")) - -response = requests.post( - "https://api.hydradb.com/context/ingest", - headers={ - "Authorization": "Bearer ", - "API-Version": "2", - }, - data={ - "type": "knowledge", - "database": "acme_corp", - "collection": "team_docs", - "document_metadata": json.dumps([ - {"id": "meeting_notes_q4", "metadata": {"department": "product"}} - ]), - }, - files={ - "documents": ("meeting-notes.txt", txt_file, "text/plain"), - }, -) -response.raise_for_status() +```bash cURL +printf 'Q4 planning notes\n\n- Launch checklist is owned by Priya.\n- Legal review is due by Friday.\n' | \ +curl -X POST 'https://api.hydradb.com/context/ingest' \ + -H "Authorization: Bearer " \ + -H "API-Version: 2" \ + -F "type=knowledge" \ + -F "database=acme_corp" \ + -F "collection=team_docs" \ + -F "documents=@-;filename=meeting-notes.txt;type=text/plain" \ + -F 'document_metadata=[{"id":"meeting_notes_q4","metadata":{"department":"product"}}]' ``` -## Important form fields +## Form fields | Name | Description | | --- | --- | | | Use singular `"memory"` when writing memories. (default=`"knowledge"`) | -| | Target database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Logical partition inside the database. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). (default=`""` - default collection) | -| | Replace existing sources with the same ID. Set to `false` to error on conflict. (default=`true`) | +| | Target database. | +| | Collection to write to. A new name creates the collection automatically. Omit it to use the default collection. | +| | Replace existing sources with the same ID. Set to `false` to error on conflict: a document or app source whose ID exists comes back `failed` with `error_code: "E9003"`. (default=`true`) | | | Binary uploads, **knowledge only**. Required when `type=knowledge` and you want HydraDB to parse documents. Omit when ingesting memories. See [Supported file formats](#supported-file-formats). (default=`[]`) | -| | One entry per file in `documents`, in the same order. If omitted, documents index with inferred defaults such as filename/title. See the item shape below. | +| | One entry per file in `documents`, in the same order. The counts must match, or the request returns `400`. If omitted, documents index with inferred defaults such as filename/title. See the item shape below. | | | Pre-extracted source objects (Slack, Notion, web pages, etc.), **knowledge only**. See the `app_knowledge` item shape below. | -| | Map of source id → your own entities + relations - replaces LLM graph extraction for each keyed source. Works for `type=knowledge` (key = a `document_metadata` id or `app_knowledge` item id) and `type=memory` (key = a memory `id`). See [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) and the shape below. | +| | Map of source id to your own entities + relations. Replaces LLM graph extraction for each keyed source. Works for `type=knowledge` (key = a `document_metadata` id or `app_knowledge` item id) and `type=memory` (key = a memory `id`). See [Bring Your Own Graph](/essentials/v2/bring-your-own-graph#using-byog-without-cypher) and the shape below. | | | Memory items, **memory only**. Required and non-empty when `type=memory`. Use plural `memories` for the form field, even though `type` is singular `memory`. See the `memories` item shape below. | - -1. **`id` must not contain a comma (`,`).** The comma is reserved as the id separator on [Ingestion Status](/api-reference/v2/endpoint/source-status) (`GET /context/status?ids=a,b`), so an `id` containing a comma cannot be looked up unambiguously. This applies to every `id` you supply - `document_metadata`, `app_knowledge`, and `memories` items. Ingesting an item whose `id` contains a comma is rejected with a `400`. - -2. **`202 Accepted` means queued, not indexed.** Ingestion is asynchronous. A successful response only confirms your sources were accepted, not that they are ready to query. Before querying, poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) with the returned IDs until each source reaches `completed` or `errored`. Alternatively, register a webhook for `indexing.status_changed` events (see [Webhooks](/essentials/v2/webhooks)). - +1. **`id` must not contain a comma (`,`):** The comma is reserved as the id separator on [Ingestion Status](/api-reference/v2/endpoint/source-status) (`GET /context/status?ids=a,b`), so an `id` containing a comma cannot be looked up unambiguously. This applies to every `id` you supply: `document_metadata`, `app_knowledge`, and `memories` items. Ingesting an item whose `id` contains a comma is rejected with a `400`. +2. **`202 Accepted` means queued, not indexed:** Ingestion is asynchronous. A successful response only confirms your sources were accepted, not that they are ready to query. Before querying, poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) with the returned IDs until each source reaches `graph_creation` (searchable), `completed`, or `errored`. Alternatively, register a webhook for `indexing.status_changed` events (see [Webhooks](/essentials/v2/webhooks)). ## Supported file formats -Anything in this table can be uploaded through `documents` and HydraDB will read it. + + +Each file in `documents` can be up to **50 MB**. A larger file returns `413` with a message naming the file, and no part of the request is processed. Split larger files before uploading. + + + +HydraDB accepts these formats through `documents`: | Type | Formats | | --- | --- | @@ -341,7 +154,7 @@ Anything in this table can be uploaded through `documents` and HydraDB will read | Images | `.png` `.jpg` `.jpeg` `.tif` `.tiff` `.webp` `.gif` `.bmp` | | Plain text | `.txt` `.md` `.markdown` `.json` | -What HydraDB extracts differs by type, and it is worth knowing which one you are uploading: +What HydraDB extracts differs by type: | Type | What gets indexed | | --- | --- | @@ -350,11 +163,11 @@ What HydraDB extracts differs by type, and it is worth knowing which one you are | Images and scans | Text read out of the picture with OCR. A photo of a page and a scan of a page both work. | | Plain text | The file as-is. | -A scanned page with nothing readable on it, or a photo that happens to contain no text, has nothing to index. Its status ends as `errored` with [`error_code` `E2002`](/api-reference/v2/error-responses#ingestion-error-codes) ("No text content could be extracted from your file"). This is final, so re-uploading the same file returns the same result. A scan with readable text on even one page indexes normally. +Files with no readable text fail with [`E2002`](/api-reference/v2/error-responses#ingestion-error-codes). Upload a version containing text. ### Formats we cannot read -These are rejected the moment you upload them, before anything is queued. You get the answer in the upload response rather than minutes later. +Unsupported files are rejected in the upload response. | Format | Upload this instead | | --- | --- | @@ -365,27 +178,11 @@ These are rejected the moment you upload them, before anything is queued. You ge | `.epub` `.wpd` | Convert to PDF or DOCX | | `.zip` | Upload the files individually | - -`.heic` is the format iPhones use for photos by default, so it is the one people hit most often without realizing. On iOS you can change this under **Settings > Camera > Formats > Most Compatible**, which makes the camera save JPEGs instead. Or export the photo as JPEG or PDF before uploading. - +Use the file's real extension. When the filename has no extension, HydraDB uses the part's `Content-Type`. Renaming an unsupported file does not convert it. A renamed file may fail during processing. Check [Ingestion Status](/api-reference/v2/endpoint/source-status) for that failure. -### How we decide the format +### When a file is not supported -The file extension is what counts. `report.pdf` is treated as a PDF because of the `.pdf`, not because of what is inside it. - -If the filename has no extension at all, the `Content-Type` you send with that part is used instead, so a file with no extension in its name, sent as `application/pdf`, is accepted. - - -Renaming a file does not convert it. A `.heic` photo renamed to `photo.pdf` passes this check, because the check reads the name, then fails later during parsing and comes back as `errored` on [`GET /context/status`](/api-reference/v2/endpoint/source-status). Upload files under their real extension. - - -### Size limit - -Each file in `documents` can be up to **50 MB**. A larger file is rejected with `413` and a message naming the file, and no part of the request is processed. Split large documents, or upload them separately. - -## When a file is not supported - -A rejected file does not stop the rest of your upload. Each file is checked on its own, so a batch of ten with one bad file still indexes the other nine. +Unsupported files do not block other files in the upload. The request returns `202` as usual. The rejected file comes back with `status: "failed"` and `error_code: "E1002"`, and every other file is queued normally: @@ -408,22 +205,21 @@ The request returns `202` as usual. The rejected file comes back with `status: " } ``` - -`results`, `success_count` and `failed_count` sit inside `data`, not at the top level, like every other v2 response. The outer `success: true` means the request was accepted; `data.success` is what tells you whether every file in it was queued. - +`data.success` is `true` only when every file was queued. Check `data.results` for individual failures. - -**Do not poll [`GET /context/status`](/api-reference/v2/endpoint/source-status) for a file rejected with `E1002`.** The file never entered the pipeline, so it has no status record, and looking it up returns `FILE_NOT_FOUND` rather than the format error you were given. The upload response is the only place `E1002` appears. Read `error_code` on each item in `data.results` and act on it there. - +Files rejected with `E1002` have no ingestion status record. Handle the format error from the upload response. + + +## Examples and item fields -## Common use-cases and their configurations +See [Metadata and Filters](/essentials/v2/metadata#2-the-two-layers) for choosing metadata fields and using them to filter results. ### Document metadata -Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can be passed alongside each uploaded document to control indexing, filtering, and display. The key list is closed - an item carrying any other key is rejected with a `400` naming it, rather than being silently dropped. See the field reference below. +Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`, `infer`, `evidence_kind`, `evidence_subject`) can be passed alongside each uploaded document to control indexing, filtering, and display. The key list is closed: an item carrying any other key is rejected with a `400` naming it, rather than being silently dropped. See the field reference below. - + ```python Python SDK @@ -477,19 +273,23 @@ Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can | Field | Description | | --- | --- | - | | Optional context ID. If set, becomes the `id` for this document (use your app's document ID for parity). Must not contain a comma (`,`) - it is reserved as the id separator on `/context/status?ids=`. | - | | Database-schema fields for filtering and search. Keys must be declared in `database_metadata_schema`. (default=`{}`) | - | | Free-form per-document fields for display or bookkeeping. To filter on these at search time, nest under `metadata_filters.additional_metadata`. (default=`{}`) | - | | Declare forceful relations to other sources. Shape: `{ "ids": ["...", "..."] }`. Surfaced via `additional_context` in `mode: "thinking"` search. | + | | Optional context ID. If set, becomes the `id` for this document (use your app's document ID to keep the same identifier in both systems). Must not contain a comma (`,`). It is reserved as the id separator on `/context/status?ids=`. | + | | Database-schema fields for filtering and search. Keys must be declared in `database_metadata_schema`. At most 16 KiB. (default=`{}`) | + | | Free-form per-document fields for display or bookkeeping. To filter on these at search time, nest under `metadata_filters.additional_metadata`. At most 1 KiB. (default=`{}`) | + | | Explicitly link this document to other sources. Shape: `{ "ids": ["...", "..."] }`. Surfaced via `additional_context` in `mode: "thinking"` search. | + | | Run model enrichment on this file. (default=`false`) | + | | How this content came to be known: `assertion`, `record`, `said`, `done`, `third_party`, or `inferred`. Any other value returns `400`. | + | | A stable handle for who or what the content is about. | + + These limits also apply to `app_knowledge` and `memories` items. See [metadata size limits](/essentials/v2/metadata#stored-metadata) for how HydraDB measures them. - - Those four are the only keys accepted. Anything else - including `title`, `type`, `url` and `timestamp` - is rejected with a `400` naming the unsupported key, rather than being silently dropped. + Those seven, plus the legacy aliases `source_id` and `file_id` (for `id`) and `document_metadata` (for `additional_metadata`), are the only keys accepted. Anything else (including `title`, `type`, `url` and `timestamp`) is rejected with a `400` naming the unsupported key, rather than being silently dropped. In particular, a document's **title is derived, not settable**. It defaults to the uploaded filename and is returned as `source_title` on query results and `title` on `/context/list`. It cannot be overridden at ingest, and [`PATCH /context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) only merges `additional_metadata` and `database_metadata`. Use `additional_metadata` for your own display fields, or ingest through `app_knowledge`, whose items carry an explicit `title`. - + - + ```python Python SDK @@ -502,11 +302,11 @@ Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can app_knowledge=json.dumps([ { "id": "slack_thread_001", - "database": "acme_corp", - "collection": "team_docs", "title": "Pricing discussion", - "type": "slack", - "content": {"text": "We agreed on three tiers..."}, + "kind": "message", + "provider": "slack", + "external_id": "1716213600.000100", + "fields": {"kind": "message", "body": "We agreed on three tiers...", "author": "alice"}, "metadata": {"channel": "product"}, } ]), @@ -521,11 +321,11 @@ Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can appKnowledge: JSON.stringify([ { id: "slack_thread_001", - database: "acme_corp", - collection: "team_docs", title: "Pricing discussion", - type: "slack", - content: { text: "We agreed on three tiers..." }, + kind: "message", + provider: "slack", + external_id: "1716213600.000100", + fields: { kind: "message", body: "We agreed on three tiers...", author: "alice" }, metadata: { channel: "product" }, }, ]), @@ -542,11 +342,11 @@ Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can -F 'app_knowledge=[ { "id": "slack_thread_001", - "database": "acme_corp", - "collection": "team_docs", "title": "Pricing discussion", - "type": "slack", - "content": { "text": "We agreed on three tiers..." }, + "kind": "message", + "provider": "slack", + "external_id": "1716213600.000100", + "fields": { "kind": "message", "body": "We agreed on three tiers...", "author": "alice" }, "metadata": { "channel": "product" } } ]' @@ -556,22 +356,26 @@ Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can | Field | Description | | --- | --- | - | | Context ID. Treated as the upsert key. Send an empty string to have one generated upstream. Must not contain a comma (`,`) - it is reserved as the id separator on `/context/status?ids=`. | - | | Target database. Must match the form-level `database`. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | - | | Logical partition inside the database. Must match the form-level `collection`. Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still accepted (deprecated). | + | | Context ID. Treated as the upsert key. Send an empty string to have one generated upstream. Must not contain a comma (`,`). It is reserved as the id separator on `/context/status?ids=`. | + | | Optional. If sent, must match the form-level `database`. | + | | Optional. If sent, must match the form-level `collection`. | | | Short title or subject shown in search results. | - | | Source category (`slack`, `notion`, `gmail`, `webpage`, etc.). Used for filtering and display. | | | Optional long-form description. | | | Canonical URL or reference link. | | | ISO-8601 timestamp (creation or last-updated). | - | | Content payload. Use `{ "text": "..." }` for plain text. Required for app sources. | - | | Database-schema fields. (default=`{}`) | - | | Free-form per-document fields. (default=`{}`) | + | | App object kind: `email`, `message`, `ticket`, `knowledge_base`, `comment`, `meeting`, or `custom`. Selects the parser for `fields`. | + | | App namespace such as `slack`, `gmail`, `jira`, or `notion`. | + | | The provider's ID for this exact item. Enables exact lookup and relation resolution. | + | | App-native content and structure (`body`, `description`, `title`, or `data`, by `kind`). `fields.kind` must match `kind`. See [App Sources](/essentials/v2/app-sources). | + | | Older generic payload, `{ "text": "..." }`. Prefer `kind` and `fields`, which let app-aware retrieval see authors, threads, and parents. | + | | Database-schema fields. At most 16 KiB. (default=`{}`) | + | | Free-form per-document fields. At most 1 KiB. (default=`{}`) | | | Optional related attachments. (default=`[]`) | - | | Forceful relations, same shape as on `metadata`. | + | | Relations to other sources: `{ "ids": [...] }` as on `document_metadata` items, or the typed `[{ "predicate", "target" }]` list described in [App Sources](/essentials/v2/app-sources#explicit-relations-between-sources). | + | | Principals allowed to retrieve this source. Omit to leave it unrestricted. A malformed principal rejects the whole request with `400`. See [Access Control](/essentials/v2/access-control). | - + ```python Python SDK @@ -626,21 +430,19 @@ Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can | Field | Description | | --- | --- | - | | Optional unique ID. Acts as the upsert key. Must not contain a comma (`,`) - it is reserved as the id separator on `/context/status?ids=`. (default=auto-generated) | - | | Short label for display in `/context/list` and search hits as `source.title`. (default=truncated `text`) | + | | Optional unique ID. Acts as the upsert key. Must not contain a comma (`,`). It is reserved as the id separator on `/context/status?ids=`. (default=auto-generated) | + | | Label returned as `title` by List Context and `source_title` by Query. (default=the memory's `id`) | | | Raw text or markdown content. Required unless `user_assistant_pairs` is provided. | | | Conversation pairs `{ user, assistant }`. Required unless `text` is provided. | | | Treat `text` as markdown for chunking. (default=`false`) | | | When `true`, HydraDB extracts the underlying preference from raw signal. (default=`false`) | | | Guides extraction when `infer: true`. Ignored when `infer: false`. | | | The user's name. Feeds inference. (default=`"User"`) | - | | TTL in seconds. Memory stops surfacing after expiry. | - | | Database-schema fields as a **JSON-stringified** object (e.g. `"{\"department\":\"legal\"}"`). Unlike `metadata` and `app_knowledge`, memory items take this as a string, not an object. (default=`""`) | - | | Free-form per-document fields as a **JSON-stringified** object. Same string-vs-object difference as `metadata` above. (default=`""`) | - | | Forceful relations within the Memories store. Shape: `{ "ids": ["...", "..."] }`. | + | | Database-schema fields as an object (e.g. `{ "department": "legal" }`), the same shape as on `document_metadata` and `app_knowledge` items. At most 16 KiB. (default=`{}`) | + | | Free-form per-memory fields as an object, like `metadata` above. At most 1 KiB. (default=`{}`) | - + ```python Python SDK @@ -700,8 +502,8 @@ Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can - - `graph_payload` is a **map of source id → graph** that **replaces LLM graph extraction** for each keyed source. For `type=knowledge`, the key is a `document_metadata` id or an `app_knowledge` item id; for `type=memory`, the key is a memory `id`. Keyed sources are still chunked and embedded, so they stay searchable. See [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) for the full guide. + + `graph_payload` is a **map of source id to graph** that **replaces LLM graph extraction** for each keyed source. For `type=knowledge`, the key is a `document_metadata` id or an `app_knowledge` item id; for `type=memory`, the key is a memory `id`. Keyed sources are still chunked and embedded, so they stay searchable. See [Bring Your Own Graph](/essentials/v2/bring-your-own-graph#using-byog-without-cypher) for the full guide. @@ -777,39 +579,34 @@ Per-document metadata (`id`, `metadata`, `additional_metadata`, `relations`) can | Field | Description | | --- | --- | | | Top-level key: a `document_metadata` id or `app_knowledge` item id for `type=knowledge`, or a memory `id` for `type=memory`. Value is that source's graph. A key matching no source returns `400`. | - | | Map keyed by a caller-local id; each value is an entity. The key is only a handle for `relations` to reference - it is not stored. | - | | Entity name. Normalized (lowercased) server-side so it matches at query time. ≤ 256 chars. | + | | Map keyed by a caller-local id; each value is an entity. The key is only a handle for `relations` to reference. It is not stored. | + | | Entity name. Normalized (lowercased) server-side so it matches at query time. At most 256 chars. | | | Entity type (e.g. `PERSON`, `POLICY`). Stored as supplied. | | | Logical grouping for the entity. Stored as supplied. | - | | Optional external id (email, URL, etc.) - display only. | + | | Optional external id (email, URL, etc.), display only. | | | Edges referencing entity-map keys. | | | Entity-map key of the source entity. | | | Entity-map key of the target entity. | - | | Relationship label, any plain string. ≤ 256 chars. | - | | Optional sentence supporting the edge. ≤ 2,000 chars. | + | | Relationship label, any plain string. At most 256 chars. | + | | Optional sentence supporting the edge. At most 2,000 chars. | | | Optional timing info (e.g. "since 2021", "in Q3"). | - - **Per-source replace mode.** Each top-level key must match a `document_metadata` id or `app_knowledge` item id for `type=knowledge`, or a memory `id` for `type=memory`, in the same request; attach graphs to multiple sources at once. Extraction is skipped for keyed sources. Caps per graph: ≤ 5,000 entities, ≤ 10,000 relations, ≤ 500 relations per entity; over-cap returns `400`. Graphs survive re-ingest (re-upload or connector re-sync re-applies the stored graph). - + **Caps per graph:** at most 5,000 entities, 10,000 relations, and 500 relations per entity; over-cap returns `400`. Graphs survive re-ingest: a re-upload or connector re-sync re-applies the stored graph. + -## Some important notes +## Notes -- **Async indexing.** `202 Accepted` means HydraDB queued the work, not that content is searchable. Poll [Ingestion Status](/api-reference/v2/endpoint/source-status) until `indexing_status` reaches `graph_creation` (searchable) or `completed` (graph-ready). -- **Multipart, not JSON.** This endpoint uses `multipart/form-data`. Stringify all JSON arrays (`metadata`, `app_knowledge`, `memories`) before placing them in the form field. -- **Declare hot schema fields upfront.** Put frequently filtered fields in `metadata`, define them in `database_metadata_schema` with `enable_match: true`, and use `additional_metadata` for free-form display/bookkeeping fields. Define filterable fields when creating the database via [Create Database](/api-reference/v2/endpoint/create-tenant). Additive schema updates exist, but newly added dense/sparse metadata lanes are not backfilled into existing Milvus collections. -- **Memory vs knowledge.** Use `type: "memory"` for memory ingestion, listing, and deletion. Use `type: "all"` on `POST /query` when results should combine both. The multipart field name for memories is always `memories`. -- **Collection defaulting.** Omitting `collection` writes to the default collection. List available collections with [List Collections](/api-reference/v2/endpoint/list-sub-tenants). +- **Multipart, not JSON:** This endpoint uses `multipart/form-data`. Stringify all JSON arrays (`document_metadata`, `app_knowledge`, `memories`) before placing them in the form field. A body that is neither a form nor JSON returns `415`. +- **Memory vs knowledge:** Use `type: "memory"` for memory ingestion, listing, and deletion. Use `type: "all"` on `POST /query` when results should combine both. The multipart field name for memories is always `memories`. +- **Collections:** A new `collection` name creates the collection on ingestion. Omitting it writes to the default collection. List available collections with [List Collections](/api-reference/v2/endpoint/list-sub-tenants).
- - **Related Resources** +## Related resources - - **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 - - **Inspect:** [List Documents](/api-reference/v2/endpoint/list-documents) helps you fetch titles and descriptions of ingested context - - **Inspect:** [Fetch Content](/api-reference/v2/endpoint/fetch-content) helps you fetch full context of a document, memory, knowledge item - - **Cleanup:** [Delete Context](/api-reference/v2/endpoint/delete-source) - +- **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 +- **Inspect:** [List Context](/api-reference/v2/endpoint/list-documents) to browse what you ingested +- **Inspect:** [Inspect Context](/api-reference/v2/endpoint/fetch-content) to read one source's original content +- **Cleanup:** [Delete Context](/api-reference/v2/endpoint/delete-source) diff --git a/api-reference/v2/endpoint/list-connector-providers.mdx b/api-reference/v2/endpoint/list-connector-providers.mdx index 2b0a7373..6db3ea21 100644 --- a/api-reference/v2/endpoint/list-connector-providers.mdx +++ b/api-reference/v2/endpoint/list-connector-providers.mdx @@ -11,15 +11,28 @@ openapi: "api-reference/v2/openapi.json GET /connectors/providers" Use a catalog entry's `provider` value as `provider` in [Create Connector](/api-reference/v2/endpoint/create-connector), and as `id` to read that provider's credential schema. +`is_alpha` and `is_beta` indicate the provider's release stage. `moveit_support` identifies the sync engine and does not change how you connect the provider. + -```bash List every provider +```python Python SDK +catalog = client.list_providers() + +affinity = client.list_providers(id="affinity") +``` + +```typescript TypeScript SDK +const headers = { Authorization: `Bearer ${process.env.HYDRA_DB_API_KEY}` }; +const catalog = await client.listProviders({}, { headers }); + +const affinity = await client.listProviders({ id: "affinity" }, { headers }); +``` + +```bash cURL curl 'https://api.hydradb.com/connectors/providers' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ -H "API-Version: 2" -``` -```bash Describe one provider curl 'https://api.hydradb.com/connectors/providers?id=affinity' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ -H "API-Version: 2" @@ -27,6 +40,8 @@ curl 'https://api.hydradb.com/connectors/providers?id=affinity' \ +TypeScript SDK 2.1.7 requires the explicit authorization header for this method. + ```json 200 List every provider @@ -107,7 +122,7 @@ For Gmail, filter on `account_email` (`additional_metadata.account_email`) to sc - `404`: with `id`, no provider has that id. Check it against the catalog. -## Related Resources +## Related resources - [Create Connector](/api-reference/v2/endpoint/create-connector): connect a provider -- [Connectors - Overview](/api-reference/v2/endpoint/connectors-overview) +- [Connectors: Overview](/api-reference/v2/endpoint/connectors-overview) diff --git a/api-reference/v2/endpoint/list-connectors.mdx b/api-reference/v2/endpoint/list-connectors.mdx index 6dac5d13..85129abe 100644 --- a/api-reference/v2/endpoint/list-connectors.mdx +++ b/api-reference/v2/endpoint/list-connectors.mdx @@ -1,15 +1,23 @@ --- title: "List Connectors" -description: "List every connector in your workspace." +description: "List every connector in your organization." openapi: "api-reference/v2/openapi.json GET /connectors" --- -Returns every connector in the workspace your API key belongs to. Pass `provider` to list only one provider's connectors, for example `?provider=slack`. +Returns every connector in the organization your API key belongs to. Pass `provider` to list only one provider's connectors, for example `?provider=slack`. Each entry has the same fields as [Get Connector](/api-reference/v2/endpoint/get-connector). Read `lifecycle` for what a connector is doing, and use [Get Connector Status](/api-reference/v2/endpoint/get-connector-status) when you need per-resource health. +```python Python SDK +connectors = client.connectors.list() +``` + +```typescript TypeScript SDK +const connectors = await client.connectors.list(); +``` + ```bash cURL curl 'https://api.hydradb.com/connectors' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -48,7 +56,7 @@ curl 'https://api.hydradb.com/connectors' \
-## Related Resources +## Related resources - [Get Connector](/api-reference/v2/endpoint/get-connector): one connector by id - [Get Connector Status](/api-reference/v2/endpoint/get-connector-status): whether a connector is working diff --git a/api-reference/v2/endpoint/list-documents.mdx b/api-reference/v2/endpoint/list-documents.mdx index ffc2e4f4..5cbf3447 100644 --- a/api-reference/v2/endpoint/list-documents.mdx +++ b/api-reference/v2/endpoint/list-documents.mdx @@ -1,17 +1,18 @@ --- title: "List Context" -description: "Browse over knowledge or memories with optional filters. Results are paginated. " +description: "Page through knowledge sources or memories, with optional filters." openapi: "api-reference/v2/openapi.json POST /context/list" --- import { Field } from "/snippets/field.jsx"; -Specify the category using the `type` parameter to filter and view ingested knowledge or user memories within a database or collection: +Use `type` to choose what to list within a database and collection: -- `type=knowledge` _(default)_ - knowledge sources (documents, app sources). -- `type=memory` - user memories. +- `type=knowledge` _(default)_: knowledge sources (documents, app sources). +- `type=memory`: user memories. +- `type=all`: knowledge sources and memories. -Supports pagination, metadata filters, and field projection. For metadata design and query-time behavior, see [Scoping using metadata](/essentials/v2/metadata). +Use pagination to browse results, filters to narrow them, and `include_fields` to return only the fields you need. See [Metadata and Filters](/essentials/v2/metadata#7-list-sources-with-metadata-filters) for the metadata workflow. @@ -23,7 +24,7 @@ sources = client.context.list( page_size=50, filters={ "metadata": {"department": "legal"}, - "source_fields": {"type": "slack"}, + "source_fields": {"title": "Compliance"}, }, include_fields=["title", "type", "timestamp", "additional_metadata"], ) @@ -37,7 +38,7 @@ const sources = await client.context.list({ pageSize: 50, filters: { metadata: { department: "legal" }, - source_fields: { type: "slack" }, + sourceFields: { title: "Compliance" }, }, includeFields: ["title", "type", "timestamp", "additional_metadata"], }); @@ -55,7 +56,7 @@ curl -X POST 'https://api.hydradb.com/context/list' \ "page_size": 50, "filters": { "metadata": { "department": "legal" }, - "source_fields": { "type": "slack" } + "source_fields": { "title": "Compliance" } }, "include_fields": ["title", "type", "timestamp", "additional_metadata"] }' @@ -67,48 +68,48 @@ curl -X POST 'https://api.hydradb.com/context/list' \ | 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`) | -| | Bucket to list. (default=`"knowledge"`) | -| | When provided and non-empty, only items with these IDs are returned (pagination \+ filters still apply). (default=`null`) | +| | Owning database. | +| | Collection scope. If omitted, the default collection is used. | +| | Context category to list. (default=`"knowledge"`) | +| | 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 exact-match filters. See [Filters](#1-filters). (default=`null`) | -| | Field projection. Only the listed fields plus `id`, `database`, `collection` are populated. Only applies to `type=knowledge`. (default=`null` - all fields) | +| | Structured exact-match filters. See [Filters](#filters). (default=`null`) | +| | Field projection. Only the listed fields plus `id`, `database`, `collection` are populated. Only applies to `type=knowledge`. (default=`null`, all fields) | +| | Principals to list as (document ACLs). Only knowledge sources those principals may see come back. Omitted, `[]`, or `["*"]` lists everything. Ignored for `type=memory`. See [Access Control](/essentials/v2/access-control). | -### 1. Filters +### 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. There are no range, contains, or OR operators on this endpoint; run multiple calls and merge client-side for OR behavior. -- **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". +- `filters` accepts the three categories below. Values must match exactly, except `source_fields.title`, which matches a case-insensitive prefix. +- All filter pairs combine with AND. When `ids` is non-empty, filters apply only to those IDs. +- Range, contains, and OR operators are not supported. For OR behavior, run separate calls and combine the results in your application. +- A `null` filter value returns `400`. ```json { "filters": { "metadata": { "department": "Finance", "region": "US" }, "additional_metadata": { "author": "Alice Smith" }, - "source_fields": { "type": "slack", "title": "standup notes" } + "source_fields": { "app_provider": "slack", "title": "standup notes" } } } ``` | 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 `additional_metadata` payload | Free-form per-document JSON. No schema declaration required. `document_metadata` is accepted as a legacy alias. | -| | Built-in source fields (`type`, `title`, `description`, `url`, `timestamp`) | Use for app-source categories or quick title lookups. | +| | Context item's schema-aligned `metadata` payload | Exact matches on database metadata fields. See [Metadata and Filters](/essentials/v2/metadata#7-list-sources-with-metadata-filters). | +| | Context item's `additional_metadata` payload | Exact matches on free-form metadata. No schema declaration is required. | +| | Built-in and connector fields listed below | `title` matches a case-insensitive prefix. Pair external IDs with `app_provider`. Unsupported keys return `400`. | -### 2. Including Fields for convenient data objects +Supported source fields are `type`, `title`, `description`, `url`, `timestamp`, `app_provider`, `app_kind`, `app_external_id`, and `app_parent_id`. -When you don't need every field on every row, pass `include_fields` to keep response payloads small. Only the listed fields are populated; omitted fields should be treated as unavailable in that response. `id`, `database`, and `collection` are always returned. +### Field projection -Allowed values are `title`, `type`, `description`, `note`, `timestamp`, `metadata`, `additional_metadata`, and `relations`. Omit or pass `null` to return everything. +When you don't need every field on every row, pass `include_fields` to keep response payloads small. Only the listed fields are populated; omitted fields should be treated as unavailable in that response. `id`, `database`, and `collection` are always returned. - - **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-source via [Inspect Context](/api-reference/v2/endpoint/fetch-content). - +Allowed values are `title`, `type`, `description`, `note`, `timestamp`, `metadata`, `additional_metadata`, and `relations`, plus the legacy names `tenant_metadata` and `document_metadata`. Omit or pass `null` to return everything. -`include_fields` only applies to `type=knowledge`. It is ignored for `type=memory`. +**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-source via [Inspect Context](/api-reference/v2/endpoint/fetch-content). @@ -117,12 +118,14 @@ Allowed values are `title`, `type`, `description`, `note`, `timestamp`, `metadat "success": true, "data": { "success": true, - "message": "Sources retrieved successfully", + "message": "Successfully fetched sources", "sources": [ { "id": "policy_main", "database": "acme_corp", "collection": "team_docs", + "tenant_id": "acme_corp", + "sub_tenant_id": "team_docs", "title": "Compliance Policy", "type": "pdf", "timestamp": "2026-05-12T08:14:00Z", @@ -152,11 +155,17 @@ Allowed values are `title`, `type`, `description`, `note`, `timestamp`, `metadat "success": true, "data": { "success": true, + "message": "Successfully fetched sources", "user_memories": [ { "memory_id": "mem_user_alex_tone", - "memory_content": "Prefers concise answers and dark mode.", - "inferred_content": "User prefers concise answers and dark mode." + "database": "acme_corp", + "collection": "team_docs", + "tenant_id": "acme_corp", + "sub_tenant_id": "team_docs", + "title": "Tone preference", + "type": "memory", + "metadata": { "user_id": "alex" } } ], "total": 1, @@ -194,20 +203,15 @@ Allowed values are `title`, `type`, `description`, `note`, `timestamp`, `metadat -When `type=memory`, `data` is a `ListUserMemoriesResponse` instead - same idea but with a `data.user_memories[]` array of memory items. - - - **Use the canonical v2 names.** Prefer `filters.metadata` and `filters.additional_metadata`. Legacy `filters.tenant_metadata` and `filters.document_metadata` are accepted for back-compat, with canonical keys winning on conflicts. - +For `type=memory`, read `data.user_memories[]`. Each entry includes a `memory_id`, source details, and metadata. Read the memory text and `inferred_content` with [Inspect Context](/api-reference/v2/endpoint/fetch-content). +- Each row includes its `database` and `collection`. +- **Memory listing unavailable:** if the memory store cannot be read, the call still returns `200` with an empty `user_memories` and `message` set to `"Memories temporarily unavailable"`. Check `message` before you treat an empty list as "no memories".
- - **Related Resources** - - - **Fetch content:** [Fetch Content](/api-reference/v2/endpoint/fetch-content) - - **Retrieve:** [Query](/api-reference/v2/endpoint/query) - - **Delete:** [Delete Context](/api-reference/v2/endpoint/delete-source) +## Related resources - +- **Fetch content:** [Inspect Context](/api-reference/v2/endpoint/fetch-content) +- **Retrieve:** [Query](/api-reference/v2/endpoint/query) +- **Delete:** [Delete Context](/api-reference/v2/endpoint/delete-source) diff --git a/api-reference/v2/endpoint/list-sub-tenants.mdx b/api-reference/v2/endpoint/list-sub-tenants.mdx index ca8e9288..1e76843d 100644 --- a/api-reference/v2/endpoint/list-sub-tenants.mdx +++ b/api-reference/v2/endpoint/list-sub-tenants.mdx @@ -4,13 +4,12 @@ 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. +Collections are created by writing to them. A new database has none: the first ingest without a `collection` creates the default collection, and the first ingest under a new `collection` value creates that one. The list grows as your application writes. ```python Python SDK -response = client.databases.collections(database="your database id") +response = client.databases.collections(database="my_first_database") ``` ```typescript TypeScript SDK @@ -78,10 +77,8 @@ curl -X GET 'https://api.hydradb.com/databases/collections?database=my_first_dat
- - **Related Resources** +## Related resources - - **Inspect content:** [List Documents](/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) - +- **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 fe12f3d7..0278c9e9 100644 --- a/api-reference/v2/endpoint/list-tenants.mdx +++ b/api-reference/v2/endpoint/list-tenants.mdx @@ -1,10 +1,10 @@ --- title: "List Databases" -description: "List all databases created. " +description: "List the databases your API key can see, including any that failed to provision." openapi: "api-reference/v2/openapi.json GET /databases" --- -The response separates active or provisioning databases (in `data.databases`) from databases whose provisioning failed (in `data.failed_databases`). Use [Database Status](https://docs.hydradb.com/api-reference/v2/endpoint/tenant-status) to confirm readiness before ingestion. +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. @@ -80,19 +80,16 @@ curl -X GET 'https://api.hydradb.com/databases' \ -## Retry notes +## Retrying a failed database -- 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. +A database in `data.failed_databases` carries diagnostic entries (see the **Provisioning issue** tab). Fix the reported issue, re-create it with `POST /databases`, and poll status again before ingesting.
- - **Related Resources** +## Related resources - - **Create:** [Create Database](/api-reference/v2/endpoint/create-tenant) - - **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/multi-tenant) - \ No newline at end of file +- **Create:** [Create Database](/api-reference/v2/endpoint/create-tenant) +- **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:** [Multi-tenancy](/essentials/v2/multi-tenant) diff --git a/api-reference/v2/endpoint/list-webhook-deliveries.mdx b/api-reference/v2/endpoint/list-webhook-deliveries.mdx index 500ac22a..c0eabdfc 100644 --- a/api-reference/v2/endpoint/list-webhook-deliveries.mdx +++ b/api-reference/v2/endpoint/list-webhook-deliveries.mdx @@ -1,10 +1,10 @@ --- title: "List Deliveries" -description: "List webhook delivery attempts for your workspace." +description: "List webhook delivery attempts for your organization." 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. +Returns the delivery history for your organization, 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`. @@ -16,4 +16,24 @@ Filter by `status` to isolate failures, and page through results with `limit` an | `failed` | An attempt failed and will be retried. | | `permanently_failed` | Retries are exhausted. No further attempts. | +`limit` is 1 to 100 (default 20). To get the next page, pass the response's `next_cursor` as `cursor`. `next_cursor` is `null` on the last page. A `limit` outside that range returns `422`, and an unknown `status` returns `400`. + See [Webhooks](/essentials/v2/webhooks) for retry behaviour. + + + +```python Python SDK +deliveries = client.webhooks.list_deliveries(status="failed", limit=20) +``` + +```typescript TypeScript SDK +const deliveries = await client.webhooks.listDeliveries({ status: "failed", limit: 20 }); +``` + +```bash cURL +curl 'https://api.hydradb.com/webhooks/indexing/deliveries?status=failed&limit=20' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + diff --git a/api-reference/v2/endpoint/pause-connector.mdx b/api-reference/v2/endpoint/pause-connector.mdx index 620b9e32..c942bb9f 100644 --- a/api-reference/v2/endpoint/pause-connector.mdx +++ b/api-reference/v2/endpoint/pause-connector.mdx @@ -10,6 +10,14 @@ While paused, `lifecycle` reads `paused` and [Sync Connector](/api-reference/v2/ +```python Python SDK +client.connectors.pause("{connector_id}") +``` + +```typescript TypeScript SDK +await client.connectors.pause({ id: "{connector_id}" }); +``` + ```bash cURL curl -X POST 'https://api.hydradb.com/connectors/{id}/pause' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -32,12 +40,12 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/pause' \ ## Errors -- `404`: no connector with this id in your workspace. +- `404`: no connector with this id in your organization. - `409`: the connector is already paused.
-## Related Resources +## Related resources - [Resume Connector](/api-reference/v2/endpoint/resume-connector) - [Delete Connector](/api-reference/v2/endpoint/delete-connector): remove the connector for good diff --git a/api-reference/v2/endpoint/query-overview.mdx b/api-reference/v2/endpoint/query-overview.mdx index 9fd25715..b78ce336 100644 --- a/api-reference/v2/endpoint/query-overview.mdx +++ b/api-reference/v2/endpoint/query-overview.mdx @@ -1,11 +1,11 @@ --- -title: "Query - Overview" -description: "Quick reference for query modes, type selection, and when to call each." +title: "Query: Overview" +description: "Choose how HydraDB searches and ranks your context." --- import { Field } from "/snippets/field.jsx"; -Use this page to choose the right query shape before opening the full [Query](/api-reference/v2/endpoint/query) endpoint reference. Query has three main decisions: what to query (`type`), how to match (`query_by`), and how much retrieval work to spend (`mode`). +A query returns ranked passages (**chunks**) and their source details. Choose what to search with `type`, how to match it with `query_by`, and how much retrieval work to do with `mode`. See [Query](/api-reference/v2/endpoint/query) for all request and response fields. ```mermaid flowchart LR @@ -35,38 +35,34 @@ linkStyle default stroke:#64748b,stroke-width:2px; | Parameter | Values | Use it for | |---|---|---| -| | `"knowledge"`, `"memory"`, `"all"` | Choose the collection. Use `"knowledge"` for shared docs/app sources, `"memory"` for user context, and `"all"` when an answer should use both. | +| | `"knowledge"`, `"memory"`, `"all"` | Choose what to query. Use `"knowledge"` for shared docs/app sources, `"memory"` for user context, and `"all"` when an answer should use both. | | | `"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. Use `"fast"` for low-latency paths, `"thinking"` for multi-query retrieval, reranking, and forceful-relation context, and `"auto"` to score the query and route to one of the two automatically (defaults to `"thinking"` when the signal is inconclusive; also overrides `graph_context` to match - **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`-`1.0` or `"auto"` | Tune hybrid query. Lower values favor BM25 keywords; higher values favor semantic similarity. | -| | object | Narrow candidates before ranking. Top-level keys match `metadata`; nested `additional_metadata` filters free-form per-source fields. | -| | `string[]` or weighted object | Query one or more user/workspace/team scopes. A list uses equal normalized weights; an object like `{ "workspace_42": 2, "user_alex": 1 }` applies relative ranking weights with at most one decimal place. Max 100 collections. | -| | boolean | Include entity/relation context with the chunks. On by default; set `false` for chunk-only responses. | -| | boolean | Adds app-aware retrieval while still querying the full selected knowledge scope. Use it for better app-source matching; it does not restrict retrieval to app sources only. | - - -For filter design, read [Usage - Metadata](/essentials/v2/metadata) before creating database schemas. For exact request fields, defaults, and response shape, use [Query](/api-reference/v2/endpoint/query). - +| | `"fast"`, `"thinking"`, `"auto"` | Choose a single pass with `"fast"`, broader retrieval with `"thinking"`, or automatic routing with `"auto"`. Defaults to `"auto"`. | +| | integer | Limit the returned chunks. Defaults to `10` for one collection. Maximum `250`. | +| | `0.0` to `1.0`, or `"auto"` | Tune hybrid query. Lower values favor keyword matching; higher values favor semantic similarity. Default `0.8`, which `"auto"` also resolves to. | +| | object | Narrow results by metadata. Top-level keys filter schema-backed fields. Free-form fields go under `additional_metadata`. | +| | `string[]` or weighted object | Search up to 100 existing collections. A list weights them equally. An object assigns relative weights, such as `{ "workspace_42": 2, "user_alex": 1 }`. | +| | boolean | Include entity/relation context with the chunks. On by default; set `false` with `mode: "fast"` for chunk-only responses (`thinking` always includes it). | +| | boolean | Improves app-source matching within knowledge queries. Enabled by default. The query still searches all selected knowledge. | ## Recommended configurations | User intent | Recommended config | |---|---| -| Fast document RAG | `type="knowledge"`, `query_by="hybrid"`, `mode="fast"`, `max_results=5-10`, `graph_context=false` | -| Highest-quality document RAG | `type="knowledge"`, `query_by="hybrid"`, `mode="thinking"`, `graph_context=true`, `alpha="auto"` | -| Personalized answer | `type="all"`, include `collection`, `query_by="hybrid"`, `mode="thinking"` | +| Fast answers from documents | `type="knowledge"`, `query_by="hybrid"`, `mode="fast"`, `max_results=5-10`, `graph_context=false` | +| Broader document search | `type="knowledge"`, `query_by="hybrid"`, `mode="thinking"` (thinking always includes graph context) | +| Personalized answer | `type="all"`, the user's `collection`, `query_by="hybrid"`, `mode="thinking"`. If shared docs live in another collection, list both in `collections` | | User preferences only | `type="memory"`, include `collection`, `query_by="hybrid"` | | Exact keyword or phrase | `type="knowledge"`, `query_by="text"`, `operator="phrase"` | -| Recent operational updates | `query_by="hybrid"`, `recency_bias=0.2-0.4`, filter to the right document type | -| Mixed or unpredictable query complexity | `query_by="hybrid"`, `mode="auto"` - let HydraDB route each query to `fast` or `thinking` | +| Recent operational updates | `query_by="hybrid"`, `recency_bias=0.6-0.8`, filter to the right document type | +| Mixed or unpredictable query complexity | `query_by="hybrid"`, `mode="auto"`: let HydraDB route each query to `fast` or `thinking` | ## Typical patterns -Use this for standard RAG over docs, PDFs, tickets, pages, or app sources. +Use this for answering questions from docs, PDFs, tickets, pages, or app sources. ```json { @@ -84,12 +80,12 @@ Use this for standard RAG over docs, PDFs, tickets, pages, or app sources. -Use this when the answer should combine shared knowledge with user-specific context. Always pass the same `collection` used at memory ingestion. +Search the shared knowledge collection and the same collection used at memory ingestion. Both collections must already exist. ```json { "database": "acme", - "collection": "user_alex", + "collections": ["company_docs", "user_alex"], "query": "What is our refund policy, and how should I explain it to this user?", "type": "all", "query_by": "hybrid", @@ -99,9 +95,9 @@ Use this when the answer should combine shared knowledge with user-specific cont - + -Use this when the same question should search several collection scopes and return one globally ranked result set. Use a list for equal weighting, or an object when one scope should influence ranking more strongly. +Search several collections and return one ranked result set. Use a list for equal weighting, or an object to give one collection more influence. ```json { @@ -121,7 +117,7 @@ Use this when the same question should search several collection scopes and retu -Use `metadata_filters` when you already know the slice you want. Top-level keys match schema-backed `metadata` fields; declare hot filters in `database_metadata_schema` with `enable_match: true`. Free-form per-source fields go under `additional_metadata` (`document_metadata` is a legacy alias). Multiple filters are ANDed exact-match constraints. +Metadata filters work like SQL `WHERE` clauses. In `metadata_filters`, top-level keys filter fields declared in `database_metadata_schema`. Free-form fields go under `additional_metadata`. All keys combine with AND. ```json { @@ -130,7 +126,7 @@ Use `metadata_filters` when you already know the slice you want. Top-level keys "type": "knowledge", "query_by": "hybrid", "metadata_filters": { - "department": "product", + "department": {"equals": "product"}, "additional_metadata": { "source": "launch_plan" } @@ -159,11 +155,13 @@ Use text query when literal wording matters: legal clauses, SKUs, error codes, I ## Response summary -`POST /query` returns ranked `data.chunks[]`, deduplicated `data.sources[]`, optional `data.graph_context`, and optional `data.additional_context` from forceful relations. Preserve `data.chunks[]` order when building prompts; HydraDB has already ranked the results. For prompt formatting and citation patterns, see [How to Use API Results](/essentials/v2/api-results). +By default, `data.llm_prompt` contains the question and retrieved context formatted for your model. Pass this string to the model to generate an answer. See [How to Use API Results](/essentials/v2/api-results#send-context-to-your-model) for the model call and response formats. + +The response includes ranked `data.chunks[]` with source IDs and titles, plus graph context and related chunks. Preserve chunk order when formatting results. ## Related sections -- [Query](/api-reference/v2/endpoint/query) - full endpoint reference -- [Usage - Query](/essentials/v2/query) - conceptual overview, retrieval modes, and ranking behavior -- [Usage - Metadata](/essentials/v2/metadata) - filtering with database and document metadata -- [Concepts - Context Graphs](/essentials/v2/context-graphs) - graph context and relation paths +- [Query](/api-reference/v2/endpoint/query): full endpoint reference +- [Usage: Query](/essentials/v2/query): conceptual overview, retrieval modes, and ranking behavior +- [Usage: Metadata](/essentials/v2/metadata): filtering with database and document metadata +- [Concepts: Context Graphs](/essentials/v2/context-graphs): graph context and relation paths diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx index f5258821..705b5705 100644 --- a/api-reference/v2/endpoint/query.mdx +++ b/api-reference/v2/endpoint/query.mdx @@ -6,122 +6,49 @@ openapi: "api-reference/v2/openapi.json POST /query" import { Field } from "/snippets/field.jsx"; -The single retrieval endpoint for everything. Use it any time you need to feed an LLM with grounded context, surface user preferences, or fetch chunks ranked by relevance. +Query is how agents read from HydraDB. It searches knowledge, memories, or both and returns ranked passages with their source details. -Three independent dimensions control behavior: +Choose what to search, how to match it, and how much retrieval work to do: - **`type`** picks **what** to query: `"knowledge"`, `"memory"`, or `"all"` (both, merged and re-ranked together). -- **`query_by`** picks **how** to match: `"hybrid"` (semantic + BM25, the default) or `"text"` (BM25 only - pair with `operator`). -- **`mode`** picks **how** to rank results: `"fast"` (single-pass, low-latency), `"thinking"` (expands query, reranks, and can include forceful-relation context), or `"auto"` (scores the query and routes to `"fast"` or `"thinking"` automatically, defaulting to `"thinking"` when the signal is inconclusive - **the default if `mode` is omitted**). +- **`query_by`** picks **how** to match: `"hybrid"` (semantic similarity and keyword matching, the default) or `"text"` (keyword matching with BM25; pair with `operator`). +- **`mode`** controls retrieval: `"fast"` for a single pass, `"thinking"` for query expansion, reranking, and explicitly linked sources, or `"auto"` to let HydraDB choose. The default is `"auto"`, which falls back to `"thinking"` when it cannot decide. -Read more about choosing the perfect mode for your use case [here](/api-reference/v2/endpoint/query-overview#recommended-configurations). - - -`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. - +[Recommended configurations](/api-reference/v2/endpoint/query-overview#recommended-configurations) maps common use cases to these three settings. ```python Python SDK result = client.query( database="acme_corp", - collection="user_alex", - query="What is our refund policy, and how should I explain it to this user?", - - # What to query: "knowledge", "memory", or "all". - type="all", - - # How to match: "hybrid" (default) or "text" (BM25). - query_by="hybrid", - - # "thinking" improves quality; use "fast" for lowest latency. - mode="thinking", - - # Ranking and response controls. - max_results=10, - alpha="auto", - recency_bias=0.2, - graph_context=True, - - # Pull author-declared related sources into additional_context. - # Only applies when mode="thinking". - query_forceful_relations=True, - - # Top-level keys match metadata; additional_metadata is per-source. - metadata_filters={ - "department": "support", - "additional_metadata": { - "source": "policy", - }, - }, - - # Short factual hint; not a hard filter. - additional_context="User is asking from the billing help center.", + collection="company_docs", + query="What is our refund policy?", + type="knowledge", + max_results=5, ) ``` ```typescript TypeScript SDK const result = await client.query({ database: "acme_corp", - collection: "user_alex", - query: "What is our refund policy, and how should I explain it to this user?", - - // What to query: "knowledge", "memory", or "all". - type: "all", - - // How to match: "hybrid" (default) or "text" (BM25). - queryBy: "hybrid", - - // "thinking" improves quality; use "fast" for lowest latency. - mode: "thinking", - - // Ranking and response controls. - maxResults: 10, - alpha: "auto", - recencyBias: 0.2, - graphContext: true, - - // Pull author-declared related sources into additional_context. - // Only applies when mode: "thinking". - queryForcefulRelations: true, - - // Top-level keys match metadata; additional_metadata is per-source. - metadataFilters: { - department: "support", - additional_metadata: { - source: "policy", - }, - }, - - // Short factual hint; not a hard filter. - additionalContext: "User is asking from the billing help center.", + collection: "company_docs", + query: "What is our refund policy?", + type: "knowledge", + maxResults: 5, }); ``` ```bash cURL curl -X POST 'https://api.hydradb.com/query' \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ -H "API-Version: 2" \ -H "Content-Type: application/json" \ -d '{ "database": "acme_corp", - "collection": "user_alex", - "query": "What is our refund policy, and how should I explain it to this user?", - "type": "all", - "query_by": "hybrid", - "mode": "thinking", - "max_results": 10, - "alpha": "auto", - "recency_bias": 0.2, - "graph_context": true, - "query_forceful_relations": true, - "metadata_filters": { - "department": "support", - "additional_metadata": { - "source": "policy" - } - }, - "additional_context": "User is asking from the billing help center." + "collection": "company_docs", + "query": "What is our refund policy?", + "type": "knowledge", + "max_results": 5 }' ``` @@ -129,7 +56,7 @@ curl -X POST 'https://api.hydradb.com/query' \ ### Querying multiple collections -Use `collections` when one query should fan out across multiple user, workspace, or team scopes. The field accepts either a list or a weighted object: +Use `collections` to search several collections in one request. The field accepts either a list or a weighted object: ```json Equal weighting { @@ -152,62 +79,21 @@ Use `collections` when one query should fan out across multiple user, workspace, } ``` -A list gives every collection equal normalized weight. An object treats values as positive relative ranking weights with at most one decimal place and normalizes them server-side. You can send at most 100 collections. When `max_results` is omitted, HydraDB uses up to 10 results per collection, capped at 1000 fanout candidates before the final ranked response is shaped. When `max_results` is set, it is the final global response cap across the merged fanout result set. - -> **Caching tip:** `collections` list order is not semantically significant for fanout selection. Sort list values before constructing cache keys; for weighted objects, sort keys and keep weights at the documented one-decimal precision so equivalent calls share the same cache entry. - -### Transforming the response into LLM context - -Use `build_string` / `buildString` from the SDK. It takes any `POST /query` result and returns a formatted plain string. - - -```python Python SDK -from hydra_db import HydraDB -from hydra_db.helpers import build_string - -client = HydraDB(token="YOUR_API_KEY") - -result = client.query( - database="your-database", - collection="your-collection", - query="How does authentication work?", - type="knowledge", - query_by="hybrid", - max_results=5, - mode="fast", - graph_context=True, -) - -context = build_string(result) -``` -```typescript TypeScript SDK -import { HydraDBClient } from "@hydradb/sdk"; -import { buildString } from "@hydradb/sdk/helpers"; - -const client = new HydraDBClient({ token: process.env.HYDRA_DB_API_KEY }); - -const result = await client.query({ - database: "your-database", - collection: "your-collection", - query: "How does authentication work?", - type: "knowledge", - queryBy: "hybrid", - maxResults: 5, - mode: "fast", - graphContext: true, -}); - -const context = buildString(result); -``` - +- A list gives every collection equal weight. An object assigns positive relative weights with at most one decimal place. HydraDB normalizes the weights. +- A request can include at most 100 collections. Missing collections return `400 INVALID_INPUT` with their names. +- Without `max_results`, HydraDB retrieves up to 10 results per collection, capped at 1,000 candidates before final ranking. +- With `max_results`, the limit applies to the final combined response across all collections. + +### Use the context to generate an answer +By default, `data.llm_prompt` contains the question and matching context as Markdown, with source references and citation instructions. Pass this string to your model to generate an answer using that context. See [How to Use API Results](/essentials/v2/api-results#send-context-to-your-model) for Python and TypeScript examples. ## Common use-cases and their configurations - + ```bash cURL @@ -253,7 +139,9 @@ result = client.query( - + + Search the collections used for shared knowledge and the user's memories. Both collections must already exist. + ```bash cURL @@ -263,7 +151,7 @@ curl -X POST 'https://api.hydradb.com/query' \ -H "Content-Type: application/json" \ -d '{ "database": "acme_corp", - "collection": "user_alex", + "collections": ["company_docs", "user_alex"], "query": "What is our refund policy, and how should I explain it to this user?", "type": "all", "query_by": "hybrid", @@ -274,7 +162,7 @@ curl -X POST 'https://api.hydradb.com/query' \ ```typescript TypeScript SDK const result = await client.query({ database: "acme_corp", - collection: "user_alex", + collections: ["company_docs", "user_alex"], query: "What is our refund policy, and how should I explain it to this user?", type: "all", queryBy: "hybrid", @@ -285,7 +173,7 @@ const result = await client.query({ ```python Python SDK result = client.query( database="acme_corp", - collection="user_alex", + collections=["company_docs", "user_alex"], query="What is our refund policy, and how should I explain it to this user?", type="all", query_by="hybrid", @@ -296,7 +184,7 @@ result = client.query( - + ```bash cURL @@ -309,8 +197,7 @@ curl -X POST 'https://api.hydradb.com/query' \ "collection": "user_alex", "query": "Does the user have any specific preferences for tone or response length?", "type": "memory", - "query_by": "hybrid", - "query_apps": true + "query_by": "hybrid" }' ``` @@ -321,7 +208,6 @@ const result = await client.query({ query: "Does the user have any specific preferences for tone or response length?", type: "memory", queryBy: "hybrid", - queryApps: true, }); ``` @@ -332,14 +218,13 @@ result = client.query( query="Does the user have any specific preferences for tone or response length?", type="memory", query_by="hybrid", - query_apps=True, ) ``` - + ```bash cURL @@ -379,7 +264,7 @@ result = client.query( - + ```bash cURL @@ -418,7 +303,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 don't 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 don't 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. @@ -426,31 +311,44 @@ result = client.query( | Name | Description | | --- | --- | -| | Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | -| | Single collection scope. Required for per-user memory queries. 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. `{"finance": 1.5, "legal": 0.8}`) to bias ranking. Up to 100 collections. Do not combine with `collection`/`sub_tenant_id`. Formerly `sub_tenant_ids`; the `sub_tenant_ids` alias is still accepted (deprecated since 2.0.1). | -| | Query terms or natural-language question. Cannot be empty. | -| | What collection to query. `"all"` runs knowledge and memory in parallel and merges by `relevancy_score`. (default=`"knowledge"`) | +| | Owning database. | +| | Single collection scope. Send the collection the content was saved in. | +| | 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. `{"finance": 1.5, "legal": 0.8}`) to bias ranking. Up to 100 collections. Do not combine with `collection`. | +| | Query terms or natural-language question. Cannot be empty. Maximum 5,000 words. | +| | Exact source titles, matched case-insensitively. Matches any listed title, then intersects with `ids` if both are sent. | +| | Caller identities used to enforce document permissions. Omitting `acl`, sending `[]`, or sending `["*"]` disables permission filtering. See [Access Control](/essentials/v2/access-control). | +| | Omit for the result object with `llm_prompt`. `"structured"` leaves out the prompt string. `"markdown"` returns the prompt string directly as `data`. | +| | What to query. `"all"` runs knowledge and memory in parallel over the same scope and merges by `relevancy_score`. (default=`"knowledge"`) | | | Retrieval method. See [Query methods](#decision-matrix). (default=`"hybrid"`) | -| | Adds an app-aware retrieval lane for app sources while still querying the full selected knowledge scope. Set `true` for better app-source matching, thread/relation traversal, exact IDs, and actor/provider hints. It does **not** limit query to only app sources. (default=`false`) | -| | BM25 operator for `query_by: "text"`. Ignored for `hybrid`. (default=`"or"`) | -| | Retrieval pipeline. Applies to `hybrid` only; ignored for `text`. `"auto"` scores the query before retrieval 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. (default=`0.0`) | -| | When `true`, includes the entity/relation graph slice in the response under `graph_context`. Set to `false` when you only need ranked chunks. Relations you supplied via [Bring Your Own Graph](/essentials/v2/bring-your-own-graph) appear here identically to extracted ones. (default=`true`) - **under `mode: "auto"`, this value is overridden by the resolved mode regardless of what you send.** | -| | Pull author-declared related sources into `additional_context`. **Only takes effect when `mode` resolves to `"thinking"`** - silently ignored in `fast` mode, and under `mode: "auto"` whether it takes effect depends on the automatic routing decision. (default=`true`) | +| | Applies to knowledge queries only. Adds app-aware retrieval for messages, threads, and linked app sources. It still searches all selected knowledge, rather than only app sources. Send `false` to disable app-aware retrieval. (default=`true`) | +| | BM25 operator for `query_by: "text"`. `"and"` or `"phrase"` with any other `query_by` returns `400`. (default=`"or"`) | +| | Retrieval pipeline. Applies to `hybrid` only; ignored for `text`. `"auto"` scores the query before retrieval and resolves it to `"fast"` or `"thinking"`, defaulting to `"thinking"` when the signal is inconclusive. (default=`"auto"`) | +| | Maximum chunks to return, from `1` to `250`. Defaults to `10` for a single collection. See [multi-collection defaults](#querying-multiple-collections) when searching several collections. Counts chunks, not bytes or model tokens. | +| | Hybrid weight (`1.0` = pure semantic, `0.0` = pure BM25). Applies to `query_by: "hybrid"` only. `"auto"` currently resolves to `0.8`. (default=`0.8`) | +| | Boost newer content. Omitted applies a mild `0.4` tilt; send `0` to turn it off. (default=`0.4`) | +| | When `true`, includes the entity/relation graph slice in the response under `graph_context`. Set to `false` when you only need ranked chunks. Relations you supplied via [Bring Your Own Graph](/essentials/v2/bring-your-own-graph#using-byog-without-cypher) appear here identically to extracted ones. (default=`true`). **`false` only takes effect in `fast` mode: `thinking`, and `auto` when it resolves to thinking, always include the graph slice.** | +| | Pull author-declared related sources into `additional_context`. **Only takes effect when `mode` resolves to `"thinking"`;** silently skipped in `fast` mode, and under `mode: "auto"` whether it takes effect depends on the automatic routing decision. (default=`true`) | | | Request-time hint to guide retrieval (e.g., "user is on the billing page"). This is different from the response `additional_context` map. (default=`null`) | -| | Deterministic narrowing before ranking. See [Filters](#decision-matrix). Each list holds at most 500 values, and the whole object is capped at 64 KiB of compact JSON, measured after operator objects are reduced to their values; over either returns `400`. (default=`null`) | +| | Filter schema-backed metadata with top-level keys and free-form fields under `additional_metadata`. See [Metadata filters](#metadata-filters). Each list holds at most 500 values, and the whole object is capped at 64 KiB of compact JSON, measured after operator objects are reduced to their values; over either returns `400`. (default=`null`) | - -**Tuning heuristics.** -
    -
  • alpha: start at 0.8. Lower toward 0.3–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.
  • -
  • recency_bias: leave at 0 for static reference material. Set 0.2–0.4 for mixed content, 0.6–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.
  • -
-
+To tune `alpha`, `recency_bias`, and `max_results` for your content, see the [Query guide](/essentials/v2/query). + +### Metadata filters + +Metadata filters work like SQL `WHERE` clauses. Top-level `metadata_filters` keys filter schema-backed `metadata`. Keys under `additional_metadata` filter free-form fields. Separate keys combine with AND. + +```json +{ + "database": "acme_corp", + "query": "What is our refund policy?", + "metadata_filters": { + "department": {"equals": "legal"}, + "additional_metadata": {"source": "policy"} + } +} +``` + +See [Metadata and Filters](/essentials/v2/metadata#3-filter-operators) for operators and examples. Filters narrow results. They do not replace the caller's `acl`. ### Decision matrix @@ -458,8 +356,7 @@ result = client.query( | Value | Queries | Best for | |---|---|---| - | `"knowledge"` *(default)* | Knowledge documents, files, and app sources | Document Q&A, RAG context. | - | `"query_apps=true"` | Full selected knowledge scope plus app-aware retrieval | App-specific Q&A that should still query non-app knowledge documents. | + | `"knowledge"` *(default)* | Knowledge documents, files, and app sources, plus app-aware retrieval unless `query_apps` is `false` | Document Q&A, RAG context. | | `"memory"` | User memories | Personalization and user preferences. | | `"all"` | Both, merged in one ranked result set | Personalized answers grounded in both shared and user-specific context. | @@ -478,74 +375,22 @@ result = client.query( |---|---|---| | `"fast"` | Single query pass | Real-time chat, autocomplete, simple lookups. | | `"thinking"` | Multi-query expansion + reranking + forceful-relation context | 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 don't 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 don't want to hand-pick per request. | - `"auto"`'s resolved pipeline isn't reported back in the response, so budget latency as thinking-level in the worst case. Omitting `mode` behaves exactly like `mode: "auto"` - set it explicitly to `"fast"` or `"thinking"` if you want a deterministic pipeline instead. + `"auto"`'s resolved pipeline isn't reported back in the response, so budget latency as thinking-level in the worst case. Set `mode` to `"fast"` or `"thinking"` when you need a predictable pipeline.
- - `metadata_filters` are hard exact-match constraints applied before ranking and re-checked after hydration. The shape combines two filter scopes: - - ```json - { - "metadata_filters": { - "department": "engineering", - "region": "us-east", - "additional_metadata": { - "source": "account_plan", - "author": "alex" - } - } - } - ``` - - | Where | What it matches | - |---|---| - | **Top-level keys** (`department`, `region`) | The source's schema-backed `metadata`. Keys must be declared in the database's `database_metadata_schema` with `enable_match: true`, otherwise they are silently ignored. | - | **Nested under `additional_metadata`** | The source's free-form per-document fields. No schema declaration required. `document_metadata` is accepted as a legacy alias. | - - Separate keys are ANDed. Each `metadata` (top-level) key takes an operator object naming the comparison: - - | Operator | Value | Matches | - | --- | --- | --- | - | `equals` | a single value | sources whose field is **exactly** that value | - | `contains` | a single value | sources whose field **holds** that value. Multi-value fields are stored comma-joined, so this matches one member of that list | - | `contains_any` | an array | sources holding **any one** of the listed values (OR/IN) | - - ```json - "metadata_filters": { - "department": { "equals": "legal" }, - "attendee_emails": { "contains": "b@company.com" }, - "tags": { "contains_any": ["alpha", "beta"] } - } - ``` - - Adding values to `contains_any` **widens** the result set. There is no ALL/AND operator within a single key, and range and fuzzy operators are not supported; run multiple queries or post-process client-side for those cases. - - Operators apply to `metadata` (top-level keys) only. Inside `additional_metadata`, use a bare scalar for an exact match or a bare array to match any listed value. - - - 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. - - - - The bare forms still work and are 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` - so `{"emails": "a@x"}` and `{"emails": ["a@x"]}` differ by one character and return different results. - - - `contains`, `contains_any` and lists 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. - - A known operator given the wrong operand type, or several operators in one object, is rejected with `400 VALIDATION_ERROR`. 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. - - `contains`, `contains_any` and `equals` are reserved key names: an object built only from them is read as an operator and can no longer exact-match a stored object, and an object whose keys are ALL operator names is rejected with `400`. Mixing an operator name with any other key (`{"contains": "a", "other": 1}`) is unaffected. A caller needing the reserved shape must rename the nested key or the field. -
+The prompt strings in these examples are abbreviated. + ```json Success { "success": true, "data": { + "llm_prompt": "# Instructions\n\nThis is the response markdown for the query: ...\n\n...\n\n## Results\n\n...\n\n### 1. Compliance Policy\nRefunds are issued within 30 days...", "chunks": [ { "chunk_uuid": "policy_main_chunk_3", @@ -562,21 +407,6 @@ result = client.query( "additional_metadata": { "author": "Legal Team" } } ], - "sources": [ - { - "id": "policy_main", - "title": "Compliance Policy", - "type": "pdf", - "description": "", - "url": "", - "timestamp": "2026-05-12T08:14:00Z", - "metadata": { "department": "legal" }, - "additional_metadata": { "author": "Legal Team" }, - "app_kind": null, - "app_provider": null, - "app_external_id": null - } - ], "graph_context": { "query_paths": [ { @@ -593,7 +423,6 @@ result = client.query( "canonical_predicate": "GOVERNS", "raw_predicate": "governs and regulates", "context": "The compliance policy governs the refund processing timeline of 30 days.", - "confidence": 0.95, "temporal_details": null, "timestamp": 1778573640.0, "relationship_id": "rel_governs_refunds", @@ -631,7 +460,6 @@ result = client.query( "canonical_predicate": "MANAGED_BY", "raw_predicate": "is managed by", "context": "Refund processing is managed by the Finance Department.", - "confidence": 0.9, "temporal_details": "Q2 2026 onwards", "timestamp": 1778573640.0, "relationship_id": "rel_managed_by_finance", @@ -682,8 +510,8 @@ result = client.query( { "success": true, "data": { + "llm_prompt": "# Instructions\n\nThis is the response markdown for the query: ...\n\nNo results found for this query.\n", "chunks": [], - "sources": [], "graph_context": { "query_paths": [], "chunk_relations": [], @@ -704,8 +532,8 @@ result = client.query( "success": false, "data": null, "error": { - "code": "INVALID_PARAMETERS", - "message": "query must not be empty" + "code": "INVALID_INPUT", + "message": "query cannot be empty" }, "meta": { "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", @@ -718,45 +546,31 @@ result = client.query( A zero-result query returns empty arrays/maps rather than an error, as shown in the **Zero results** tab. -## Behavior notes - - -**Default Behaviors** -- **`mode` defaults to `"auto"`.** Omitting `mode` entirely behaves exactly like `mode: "auto"` - set it explicitly to `"fast"` or `"thinking"` if you want a deterministic pipeline. -- **`graph_context` is on by default.** Set it to `false` if you only need ranked chunks and want to drop the graph slice from the response. -- **`recency_bias` is off by default.** Defaults to `0.0` - no recency boost is applied unless you set it. - - - -**Important Considerations & Common Mistakes** -- **`query_forceful_relations` requires `mode` to resolve to `"thinking"`.** In `fast` mode the flag is silently ignored. The server does not error or warn - your `additional_context` will simply be empty. Under `mode: "auto"` this depends on that request's routing decision, not on what you asked for. -- **`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, since it defaults to `"auto"`. Set `graph_context` explicitly only when calling `"fast"` or `"thinking"` directly. -- **Want a deterministic pipeline instead of automatic routing?** Set `mode` explicitly to `"fast"` or `"thinking"` - an omitted `mode` field now defaults to `"auto"`, not `"fast"`. -- **Relation `timestamp` is a Unix epoch float here.** In the `graph_context` slice returned by `/query` - and in the passthrough relations returned by [List Documents](/api-reference/v2/endpoint/list-documents) with `include_fields: ["relations"]` - each relation's `timestamp` is a Unix epoch value in seconds (a float, e.g. `1778573640.0`). The dedicated [Context Relations](/api-reference/v2/endpoint/source-relations) endpoint returns the same field as an ISO-8601 string instead. Normalize before comparing relation timestamps across endpoints. -- **Use the right metadata namespace.** Top-level `metadata_filters` keys match `metadata`; free-form per-document fields must be nested under `additional_metadata` (`document_metadata` is only a legacy alias). Declare hot top-level filter fields in `database_metadata_schema` with `enable_match: true`. -- **Common mistakes.** Check [Ingestion Status](/api-reference/v2/endpoint/source-status) for recently ingested documents before querying. If you omit `collection`, HydraDB queries the default collection; use [List Collections](/api-reference/v2/endpoint/list-sub-tenants) to discover available IDs. - + + +## Common mistakes + +- **`query_forceful_relations` requires `mode` to resolve to `"thinking"`:** In `fast` mode the flag is silently skipped. The server does not error or warn; your `additional_context` will simply be empty. Under `mode: "auto"` this depends on that request's routing decision, not on what you asked for. +- **Relation `timestamp` is a Unix epoch float here:** In the `graph_context` slice returned by `/query` (and in the passthrough relations returned by [List Context](/api-reference/v2/endpoint/list-documents) with `include_fields: ["relations"]`), each relation's `timestamp` is a Unix epoch value in seconds (a float, e.g. `1778573640.0`). The dedicated [Context Relations](/api-reference/v2/endpoint/source-relations) endpoint returns the same field as an ISO-8601 string instead. Normalize before comparing relation timestamps across endpoints. +- **Use the right metadata namespace:** Top-level `metadata_filters` keys filter schema-backed `metadata`. Filter free-form fields with `metadata_filters.additional_metadata` (`document_metadata` is a legacy alias). Declare frequently filtered fields in `database_metadata_schema`. +- **Check indexing first:** New documents are not searchable until [Ingestion Status](/api-reference/v2/endpoint/source-status) reports them indexed. +- **Collection scope:** If you omit `collection`, HydraDB queries the default collection, and a query that names a collection does not read the default one. Use [List Collections](/api-reference/v2/endpoint/list-sub-tenants) to discover available IDs. ## Errors -Common codes: `400 INVALID_PARAMETERS` (empty `query`), `404 DATABASE_NOT_FOUND`, `422 VALIDATION_ERROR`, `500 INTERNAL_ERROR`. See [Error Responses](/api-reference/v2/error-responses) for the full list. +Common codes: `400 INVALID_INPUT` (empty `query`, `operator: "and"` or `"phrase"` without `query_by: "text"`, a malformed filter operator, or a listed collection that does not exist), `400 VALIDATION_ERROR` (a metadata filter that does not fit the declared field type), `404 DATABASE_NOT_FOUND`, `422 TENANT_INFRA_NOT_READY` (the database is still provisioning; poll [Database Status](/api-reference/v2/endpoint/tenant-status) until `ready_for_ingestion` is `true`), `500 INTERNAL_ERROR`. See [Error Responses](/api-reference/v2/error-responses) for the full list. -`400` also covers oversized filters: a `metadata_filters` list above 500 values, or -a `metadata_filters` object above 64 KiB of compact JSON. The message names the -offending key or reports the actual byte count. See -[Filter size limits](/essentials/v2/metadata#filter-size-limits). +`400` also covers oversized filters: a `metadata_filters` list above 500 values, or a `metadata_filters` object above 64 KiB of compact JSON. The message names the offending key or reports the actual byte count. See [Filter size limits](/essentials/v2/metadata#filter-size-limits).
- -**Related Resources** - -- **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`) -- **Graph follow-up:** [Context Relations](/api-reference/v2/endpoint/source-relations) - inspect relationships in detail -- **Concepts:** [Usage → Query](/essentials/v2/query) -- **Concepts:** [Concepts → Semantic Search](/essentials/v2/semantic-search) -- **Concepts:** [Concepts → Context Graphs](/essentials/v2/context-graphs) -- **Response handling:** [Usage → How to Use API Results](/essentials/v2/api-results) -- **Read more:** [Query - Overview](/api-reference/v2/endpoint/query-overview) - +## Related resources + +- **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`) +- **Graph follow-up:** [Context Relations](/api-reference/v2/endpoint/source-relations): inspect relationships in detail +- **Concepts:** [Usage: Query](/essentials/v2/query) +- **Concepts:** [Concepts: Semantic Search](/essentials/v2/semantic-search) +- **Concepts:** [Concepts: Context Graphs](/essentials/v2/context-graphs) +- **Response handling:** [Usage: How to Use API Results](/essentials/v2/api-results) +- **Read more:** [Query: Overview](/api-reference/v2/endpoint/query-overview) diff --git a/api-reference/v2/endpoint/register-webhook.mdx b/api-reference/v2/endpoint/register-webhook.mdx index 27d0d798..f4d020cc 100644 --- a/api-reference/v2/endpoint/register-webhook.mdx +++ b/api-reference/v2/endpoint/register-webhook.mdx @@ -1,13 +1,47 @@ --- title: "Register Webhook" -description: "Register or update the indexing webhook for your workspace." +description: "Register or update the indexing webhook for your organization." openapi: "api-reference/v2/openapi.json POST /webhooks/indexing" --- -Registers the endpoint HydraDB calls when ingested content reaches a terminal indexing state. One webhook is registered per workspace, so calling this again replaces the current registration. +Registers the endpoint HydraDB calls when ingested content reaches a terminal indexing state. One webhook is registered per organization, so calling this again replaces the current registration. - - Omitting `signing_secret` **preserves** any secret you already have. Editing the URL or the event list never changes your signing configuration. To disable signing, call `DELETE /webhooks/indexing/signing-secret` explicitly. See [Manage the signing secret](/essentials/v2/webhooks#manage-the-signing-secret). - +Omitting `signing_secret` **preserves** any secret you already have. Editing the URL or the event list never changes your signing configuration. To disable signing, call `DELETE /webhooks/indexing/signing-secret` explicitly. See [Manage the signing secret](/essentials/v2/webhooks#manage-the-signing-secret). + +To have HydraDB create a signing secret, send `generate_signing_secret: true`. The secret comes back once, in this response, and cannot be read again. Send `generate_signing_secret` or `signing_secret`, not both. Your endpoint must be reachable over public HTTPS. Localhost and private network addresses are rejected. See [Webhooks](/essentials/v2/webhooks) for the payload format, retry behaviour, and receiver examples. + + + +```python Python SDK +webhook = client.webhooks.register( + url="https://example.com/hydradb/webhooks", + event_types=["indexing.status_changed"], + generate_signing_secret=True, +) +secret = webhook.data.signing_secret # returned once; store it now +``` + +```typescript TypeScript SDK +const webhook = await client.webhooks.register({ + url: "https://example.com/hydradb/webhooks", + eventTypes: ["indexing.status_changed"], + generateSigningSecret: true, +}); +const secret = webhook.data?.signingSecret; // returned once; store it now +``` + +```bash cURL +curl -X POST 'https://api.hydradb.com/webhooks/indexing' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com/hydradb/webhooks", + "event_types": ["indexing.status_changed"], + "generate_signing_secret": true + }' +``` + + diff --git a/api-reference/v2/endpoint/resume-connector.mdx b/api-reference/v2/endpoint/resume-connector.mdx index 5a14e26b..9b9d0c83 100644 --- a/api-reference/v2/endpoint/resume-connector.mdx +++ b/api-reference/v2/endpoint/resume-connector.mdx @@ -8,6 +8,14 @@ Puts a paused connector back on its schedule and makes it due for a sync right a +```python Python SDK +client.connectors.resume("{connector_id}") +``` + +```typescript TypeScript SDK +await client.connectors.resume({ id: "{connector_id}" }); +``` + ```bash cURL curl -X POST 'https://api.hydradb.com/connectors/{id}/resume' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -29,12 +37,12 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/resume' \ ## Errors -- `404`: no connector with this id in your workspace. +- `404`: no connector with this id in your organization. - `409`: the connector is not paused.
-## Related Resources +## Related resources - [Pause Connector](/api-reference/v2/endpoint/pause-connector) - [Get Connector Status](/api-reference/v2/endpoint/get-connector-status): check the next sync succeeds diff --git a/api-reference/v2/endpoint/retry-webhook-delivery.mdx b/api-reference/v2/endpoint/retry-webhook-delivery.mdx index 836491eb..c7c78833 100644 --- a/api-reference/v2/endpoint/retry-webhook-delivery.mdx +++ b/api-reference/v2/endpoint/retry-webhook-delivery.mdx @@ -4,8 +4,26 @@ 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` or `permanently_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. For a delivery in any other state, the call returns `queued: false` and nothing is retried. 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. See [Webhooks](/essentials/v2/webhooks) for retry and backoff behaviour. + + + +```python Python SDK +result = client.webhooks.retry_delivery("{delivery_id}") +``` + +```typescript TypeScript SDK +const result = await client.webhooks.retryDelivery({ deliveryId: "{delivery_id}" }); +``` + +```bash cURL +curl -X POST 'https://api.hydradb.com/webhooks/indexing/deliveries/{delivery_id}/retry' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + diff --git a/api-reference/v2/endpoint/set-webhook-signing-secret.mdx b/api-reference/v2/endpoint/set-webhook-signing-secret.mdx new file mode 100644 index 00000000..d38bb448 --- /dev/null +++ b/api-reference/v2/endpoint/set-webhook-signing-secret.mdx @@ -0,0 +1,37 @@ +--- +title: "Set Signing Secret" +description: "Generate or replace the secret used to sign webhook deliveries." +openapi: "api-reference/v2/openapi.json POST /webhooks/indexing/signing-secret" +--- + +Generates a signing secret for an existing webhook, or replaces it with the `signing_secret` you supply. A supplied secret must contain at least 16 characters. The secret is returned once in `data.signing_secret`; store it securely, since it cannot be read back. + +Rotation takes effect immediately. If you supply your own secret, configure your receiver to accept it before making this call. See [Secret rotation](/essentials/v2/webhooks#manage-the-signing-secret). + + + +```python Python SDK +result = client.webhooks.set_signing_secret() +secret = result.data.signing_secret +``` + +```typescript TypeScript SDK +const result = await client.webhooks.setSigningSecret(); +const secret = result.data?.signingSecret; +``` + +```bash cURL +curl -X POST 'https://api.hydradb.com/webhooks/indexing/signing-secret' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + + +To enable signing when creating a webhook, use `generate_signing_secret: true` on [Register Webhook](/api-reference/v2/endpoint/register-webhook). If no webhook is registered, this endpoint returns `404`. + +## Related resources + +- [Webhooks guide](/essentials/v2/webhooks): verify signatures on your receiver +- [Send Test Delivery](/api-reference/v2/endpoint/test-webhook): check signature verification +- [Disable Signing](/api-reference/v2/endpoint/disable-webhook-signing): remove the secret diff --git a/api-reference/v2/endpoint/source-relations.mdx b/api-reference/v2/endpoint/source-relations.mdx index ca819275..5cec5268 100644 --- a/api-reference/v2/endpoint/source-relations.mdx +++ b/api-reference/v2/endpoint/source-relations.mdx @@ -1,12 +1,12 @@ --- title: "Inspecting Context Relations" openapi: "api-reference/v2/openapi.json GET /context/relations" -description: "See and explore relationships that create the brain for your AI. " +description: "Read the entity-relation triplets extracted from your content, for one source or a whole collection." --- import { Field } from "/snippets/field.jsx"; -This endpoint queries entity-and-relationship triplets extracted from your ingested content. +Inspect relationships extracted from your content, such as `PaymentsWorker` depends on `OrdersDB`. Each three-part relationship is a **triplet**: a starting entity (a named person, service, or topic), a relationship, and a target entity. Pass `id` to scope to a single ingested item, or omit it to return all relations in the collection. Set `type=memory` to inspect a memory's relations. Pagination handles large result sets. @@ -43,12 +43,13 @@ curl -G 'https://api.hydradb.com/context/relations' \ | Name | Description | | --- | --- | -| | Owning database. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | +| | Owning database. | | | When provided, returns relations for that specific source. When omitted, returns all relations across the collection. (default=`null`) | -| | Bucket selector. Use `"memory"` when `id` belongs to a memory item. (default=`"knowledge"`) | -| | 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–10000`. (default=`5000`) | +| | Context category. Use `"memory"` when `id` belongs to a memory item. (default=`"knowledge"`) | +| | Collection scope. If omitted, the default collection is used. | +| | 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 (document ACLs). Only relations from sources those principals may see are returned. Repeated (`acl=a&acl=b`) or comma-separated. Omit for no ACL scoping. See [Access Control](/essentials/v2/access-control). | @@ -63,14 +64,16 @@ curl -G 'https://api.hydradb.com/context/relations' \ "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": [ { @@ -89,10 +92,12 @@ curl -G 'https://api.hydradb.com/context/relations' \ "chunk_id": "policy_main_chunk_3" } ], + "auxiliary_relations": [], + "auxiliary_truncated": false, "is_truncated": false, "next_cursor": null, "success": true, - "message": "Relations retrieved successfully" + "message": "Successfully fetched relations for source" }, "error": null, "meta": { @@ -112,30 +117,41 @@ curl -G 'https://api.hydradb.com/context/relations' \ "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.", + "confidence": 0.88, + "temporal_details": null, + "timestamp": "2026-05-12T08:14:00Z", "relationship_id": "rel_payments_orders", - "confidence": 0.88 + "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, + "next_cursor": 1778573640.0, "success": true, - "message": "Relations retrieved successfully" + "message": "Successfully fetched relations for source" }, "error": null, "meta": { @@ -150,8 +166,8 @@ curl -G 'https://api.hydradb.com/context/relations' \ "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", @@ -175,30 +191,35 @@ while True: limit=1000, cursor=cursor, ) - all_relations.extend(page.relations) - if page.next_cursor is None: + all_relations.extend(page.data.relations) + if page.data.next_cursor is None: + if page.data.is_truncated: + raise RuntimeError( + "Relations export is incomplete. Retry this page or increase limit." + ) break - cursor = page.next_cursor + cursor = page.data.next_cursor ``` -## Some additional notes +## 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 the timestamp, in Unix seconds, of the last group returned). 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. -- **Knowledge vs memory:** If the `id` belongs to a memory, set `type=memory`; otherwise the endpoint searches the Knowledge graph. The two graphs are completely separate. -- **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. +- **Full-graph exports:** omit `id`, use a small `limit`, and paginate. +- **Ordering:** `data.relations[]` comes back newest first, by each group's latest relation `timestamp`, not by relevance. +- **Unknown `id`:** returns `200` with an empty `relations` list, not an error. So does an `id` the `acl` principals may not see. +- **`auxiliary_relations`:** the structural graph around the sources (who sent a message, which comments hang off it), in the same triplet shape. It does not count against `limit`; `auxiliary_truncated` is `true` when it was clipped. - **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. -- **`timestamp` format differs by endpoint.** On this endpoint each relation's `timestamp` is an ISO-8601 string (e.g. `2026-05-12T08:14:00Z`). The same relations surfaced as passthrough on [Query](/api-reference/v2/endpoint/query) (in `graph_context`) and [List Documents](/api-reference/v2/endpoint/list-documents) carry `timestamp` as a Unix epoch float (seconds) instead. Normalize before comparing relation timestamps across endpoints. +- **`timestamp` format differs by endpoint:** On this endpoint each relation's `timestamp` is an ISO-8601 string (e.g. `2026-05-12T08:14:00Z`). The same relations surfaced as passthrough on [Query](/api-reference/v2/endpoint/query) (in `graph_context`) and [List Context](/api-reference/v2/endpoint/list-documents) carry `timestamp` as a Unix epoch float (seconds) instead. Normalize before comparing relation timestamps across endpoints. + +## Errors + +Common codes: `400 INVALID_INPUT` (missing `database`, a `limit` outside `1` to `10000`, or a non-numeric `cursor`), `404 DATABASE_NOT_FOUND`, `500 INTERNAL_ERROR` (a transient graph read failure; retry the request). See [Error Responses](/api-reference/v2/error-responses) for the full list.
- - **Related Resources** +## Related resources - - **Indexing status:** [Ingestion Status](/api-reference/v2/endpoint/source-status) - confirm the graph is complete - - **Query with graph context:** [Query](/api-reference/v2/endpoint/query) with `graph_context: true` - - **Concepts:** [Concepts → Context Graphs](/essentials/v2/context-graphs) - +- **Indexing status:** [Ingestion Status](/api-reference/v2/endpoint/source-status), to confirm the graph is complete +- **Query with graph context:** [Query](/api-reference/v2/endpoint/query) with `graph_context: true` +- **Concepts:** [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 3d02557f..a45f7468 100644 --- a/api-reference/v2/endpoint/source-status.mdx +++ b/api-reference/v2/endpoint/source-status.mdx @@ -1,6 +1,6 @@ --- title: "Ingestion Status" -description: "Check the processing status of ingested documents. " +description: "Check whether ingested documents, app sources, or memories are ready to query." openapi: "api-reference/v2/openapi.json GET /context/status" --- @@ -8,11 +8,9 @@ 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 documents, app sources, and memories. 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 [Knowledge](/essentials/v2/knowledge) and [Memories](/essentials/v2/memories) guides. +Pass the IDs you got back from ingestion in `ids` (or a single `id`). It works for documents, app sources, and memories. Whitespace is trimmed, empty entries are dropped, and duplicates are removed. - - **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. - +**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,9 +45,10 @@ curl -G 'https://api.hydradb.com/context/status' \ | Name | Description | | --- | --- | -| | One or more `id` values returned at ingestion. Accepts IDs for documents, app sources, or memories. 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. | -| | 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`) | +| | One or more `id` values returned at ingestion. Accepts IDs for documents, app sources, or memories. 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. | +| | A single ID. Merged with `ids` when you send both. At least one ID across `id` and `ids` is required. | +| | Database the items belong to. | +| | Collection scope. If omitted, the default collection is used. | @@ -62,23 +61,20 @@ curl -G 'https://api.hydradb.com/context/status' \ "id": "policy_main", "indexing_status": "completed", "error_code": "", - "success": true, "message": "Processing status retrieved successfully" }, { "id": "runbook_deploy", "indexing_status": "graph_creation", "error_code": "", - "success": true, "message": "Processing status retrieved successfully" }, { "id": "typo_in_id", "indexing_status": "errored", "error_code": "FILE_NOT_FOUND", - "error_message": "ID not found", - "success": false, - "message": "Processing status retrieved successfully" + "error_message": "", + "message": "ID not found" } ] }, @@ -115,24 +111,22 @@ Each entry in `data.statuses` describes one requested `id`: | --- | --- | --- | | `id` | string | The source, app-source, or memory 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_code` | string | Reason for an error. Empty otherwise. See [error code values](#error-code-values). | +| `error_message` | string | Explanation of an ingestion failure. Empty otherwise, including for `FILE_NOT_FOUND`. | +| `message` | string | Lookup message. Use `indexing_status` and `error_code` to check ingestion. | ### 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` explains why an ID was not found or ingestion failed. It is empty unless `indexing_status` is `errored`. | `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` | The ID was not found in this database and collection. | Check the ID and scope, or ingest the source. | +| `E####` | Ingestion failed. | Follow the [error code guidance](/api-reference/v2/error-responses#ingestion-error-codes). | + +Status records can expire. A source that completed still reports `completed` after its record expires. A source that never finished before its record expired reports `errored` with `E9004`; ingest it again. - - 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). - +Use `error_code` in your application logic. Message text may change. ## Status values @@ -167,11 +161,11 @@ flowchart LR | --- | --- | --- | | `queued` | No | Accepted by the server, not yet picked up by a worker. | | `processing` | No | Content is being parsed, chunked, and embedded. | -| `graph_creation` | **Yes** | Indexed and retrievable; the knowledge graph is still being built. Already searchable via `/query`, but graph context may still be incomplete. | +| `graph_creation` | **Yes** | Searchable through `/query`. Graph processing is still in progress. | | `completed` | Yes | Fully indexed and graphed. Ready for all retrieval modes. | | `errored` | No | Processing failed. Inspect `error_code` and `error_message`. | -The normal progression is `queued` → `processing` → `graph_creation` → `completed`. Treat `errored` as terminal. +The normal progression is `queued`, then `processing`, then `graph_creation`, then `completed`. Treat `errored` as terminal. ## Polling patterns @@ -194,7 +188,7 @@ while True: ) statuses = [s.indexing_status for s in response.data.statuses] - if all(s in ("graph_creation", "completed") for s in statuses): + if len(statuses) == len(ids) and all(s in ("graph_creation", "completed") for s in statuses): break if any(s == "errored" for s in statuses): raise RuntimeError("Context processing failed") @@ -212,8 +206,8 @@ while (true) { ids: ids, }); - const statuses = response.data.statuses.map((s) => s.indexingStatus); - if (statuses.every((s) => s === "graph_creation" || s === "completed")) break; + const statuses = response.data?.statuses?.map((s) => s.indexingStatus) ?? []; + if (statuses.length === ids.length && statuses.every((s) => s === "graph_creation" || s === "completed")) break; if (statuses.some((s) => s === "errored")) throw new Error("Context processing failed"); await new Promise((r) => setTimeout(r, 5000)); @@ -227,15 +221,19 @@ while (true) { Use this before graph-heavy operations such as `/context/relations` or when you require complete `graph_context`. ```python +import time + +ids = ["policy_main", "runbook_deploy"] + while True: response = client.context.status( database="acme_corp", collection="team_docs", - ids=["policy_main", "runbook_deploy"], + ids=ids, ) statuses = [s.indexing_status for s in response.data.statuses] - if all(s == "completed" for s in statuses): + if len(statuses) == len(ids) and all(s == "completed" for s in statuses): break if any(s == "errored" for s in statuses): raise RuntimeError("Graph processing failed") @@ -246,30 +244,20 @@ while True: Typical processing time: - **Memories** (text, markdown, conversation pairs): seconds -- **Small documents** (under 50 pages): 1–5 minutes -- **Large documents** (50\+ pages): 5–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_context: true`). - - -- **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). +- **Small documents** (under 50 pages): 1 to 5 minutes +- **Large documents** (50\+ pages): 5 to 15 minutes ## Errors -Common codes: `400 INVALID_PARAMETERS`, `404 DATABASE_NOT_FOUND`, `422 VALIDATION_ERROR`. See [Error Responses](/api-reference/v2/error-responses) for the full list. +Common codes: `400 INVALID_INPUT` (for example `database` and `tenant_id` both sent with different values), `404 DATABASE_NOT_FOUND`, `422 VALIDATION_ERROR` (missing `database`, or no ID). See [Error Responses](/api-reference/v2/error-responses) for the full list.
- - **Related Resources** +## Related resources - - **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:** [Context Relations](/api-reference/v2/endpoint/source-relations) - - **Read more:** [Usage → Knowledge](/essentials/v2/knowledge) - - **Read more:** [Usage → Memories](/essentials/v2/memories) - +- **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) +- **Read more:** [Usage: Knowledge](/essentials/v2/knowledge) +- **Read more:** [Usage: Memories](/essentials/v2/memories) diff --git a/api-reference/v2/endpoint/sources-overview.mdx b/api-reference/v2/endpoint/sources-overview.mdx index aba256b2..a521d9b0 100644 --- a/api-reference/v2/endpoint/sources-overview.mdx +++ b/api-reference/v2/endpoint/sources-overview.mdx @@ -1,6 +1,6 @@ --- -title: "Context Management - Overview" -description: "Quick reference for context management endpoints, their lifecycle, and when to use which." +title: "Context Management: Overview" +description: "Ingest, inspect, and manage knowledge and memories." --- ## Endpoint references @@ -13,8 +13,10 @@ description: "Quick reference for context management endpoints, their lifecycle, | Poll indexing progress | `/context/status` | | Browse stored sources or memories | `/context/list` | | Inspect original source content | `/context/inspect` | +| Update a source's metadata without re-ingesting | `PATCH /context/{id}/metadata` | | Delete sources or memories | `DELETE /context` | | Inspect graph relations | `/context/relations` | +| Get everything connected to one item | `/context/{id}/subgraph` | ## Lifecycle @@ -53,49 +55,43 @@ flowchart LR style E fill:#0f172a,stroke:#ef4444,stroke-width:2px,color:#f8fafc ``` - - **Why both** `type=knowledge `**and** `app_knowledge`**?** They have a theoretical differentiation. +**Why both `type=knowledge` and `app_knowledge`?** They answer different questions. - - `type` picks the **bucket**: `knowledge` (shared documents) or `memory` (per-user context). It routes the ingest to the right store. - - Within `type=knowledge`, you pick the **payload shape**: `documents` (binary documents HydraDB will parse - PDFs, DOCX, CSV) or `app_knowledge` (a JSON array of already-extracted content from your app - Slack messages, Notion pages, web pages). You can send both in the same request. - +- `type` picks the **store**: `knowledge` (documents, app sources, and facts) or `memory` (preferences, decisions, and past conversations). It routes the ingest to the right store. +- Within `type=knowledge`, you pick the **payload shape**: `documents` (binary documents HydraDB will parse: PDFs, DOCX, CSV) or `app_knowledge` (a JSON array of already-extracted content from your app: Slack messages, Notion pages, web pages). You can send both in the same request. -## Core Ingestion Concepts +## Core ingestion concepts -- **Knowledge vs. Memories**: [Knowledge](/essentials/v2/knowledge) is shared, database-wide content (documents, app pages, Slack messages). [Memories](/essentials/v2/memories) are user-specific preferences and conversational traits scoped by `collection`. Both can be searched together via `type: "all"` on `POST /query`. -- **IDs**: Unique identifiers returned by `/context/ingest`. You can assign custom IDs using `id` in metadata or `id` in `app_knowledge` items. Use them for polling status, inspecting content, and deleting context. -- **Metadata Filtering**: You can scope queries using `metadata` (structured fields defined in your database schema) or `additional_metadata` (free-form per-document JSON). For detailed guidelines on structuring metadata, see the [Scoping using metadata](/essentials/v2/metadata) guide. -- **Forceful Relations**: Relationships between sources can be declared at ingestion time to construct a robust knowledge graph. For more details on the graph layer, see the [Context Graphs](/essentials/v2/context-graphs) guide. +- **Knowledge vs. memories:** [Knowledge](/essentials/v2/knowledge) includes documents, app sources, and facts your AI uses. [Memories](/essentials/v2/memories) are preferences, decisions, and past conversations. Both live in a `collection`, and `type: "all"` on `POST /query` searches both from the same scope. +- **IDs:** Unique identifiers returned by `/context/ingest`. You can assign custom IDs using `id` in `document_metadata`, `app_knowledge`, or `memories` items. Use them for polling status, inspecting content, and deleting context. +- [Metadata and Filters](/essentials/v2/metadata) covers metadata fields, ingestion, updates, and filtering. ## Forceful relations and metadata -Forceful relations let you pre-wire document relationships at ingestion time so that relevant documents surface together during retrieval - even before the graph layer discovers connections organically. Think of them as explicit "see also" links between your documents. +Use `relations.ids` to add explicit "see also" links between documents. In thinking mode, HydraDB can return the linked context alongside the matching passages. -Paired with document-level metadata, you get deterministic control over how results are filtered and ranked. +Linked sources come back in the query response's `additional_context` (thinking mode). Set relations on the same `document_metadata` item as the file's metadata: ```python Python SDK -result = client.context.ingest( - type="knowledge", - database="acme_corp", - documents=[("runbook.pdf", f, "application/pdf")], - document_metadata=json.dumps([{ - "id": "runbook_deploy", - "metadata": {"department": "ops"}, - "additional_metadata": {"owner": "platform-team"}, - "relations": {"ids": ["monitoring_guide"]}, - }]), -) +import json + +with open("runbook.pdf", "rb") as f: + result = client.context.ingest( + type="knowledge", + database="acme_corp", + documents=[("runbook.pdf", f, "application/pdf")], + document_metadata=json.dumps([{ + "id": "runbook_deploy", + "metadata": {"department": "ops"}, + "additional_metadata": {"owner": "platform-team"}, + "relations": {"ids": ["monitoring_guide"]}, + }]), + ) ``` -## Related sections +## Related -- [Usage - Forceful Relations](/essentials/v2/knowledge) - linking sources at ingestion (see §7) -- [Query](/api-reference/v2/endpoint/query-overview) - retrieve ingested content - - - Related Resources - - - [Usage - Memories](/essentials/v2/memories) - memories vs knowledge, when to use which - - - [Usage - Metadata](/essentials/v2/metadata) - database-level vs document-level metadata - +- [Knowledge: forceful relations](/essentials/v2/knowledge#7-forceful-relations): linking sources at ingestion +- [Memories](/essentials/v2/memories): memories vs knowledge, when to use which +- [Metadata](/essentials/v2/metadata): database-level vs document-level metadata +- [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 e8c9a8d9..a8ff92ad 100644 --- a/api-reference/v2/endpoint/subgraph.mdx +++ b/api-reference/v2/endpoint/subgraph.mdx @@ -1,17 +1,35 @@ --- title: "Connected Subgraph" openapi: "api-reference/v2/openapi.json GET /context/{id}/subgraph" -description: "Everything connected to one item: its thread, its replies, its parents and children, and the items it links to." +description: "Retrieve the thread, parent and child items, and explicit links around one source." --- 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). +Use this endpoint when a search result needs more context, such as the replies to a Slack message. Pass an ID from [Query](/api-reference/v2/endpoint/query) or [List Context](/api-reference/v2/endpoint/list-documents) to retrieve the starting item, connected items, and their relationships. -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. +The endpoint follows threads, parent-child relationships, and explicit links between sources. For relationships extracted from text, such as service dependencies, use [Inspecting Context Relations](/api-reference/v2/endpoint/source-relations). +```python Python SDK +subgraph = client.context.subgraph( + id="slack_C0BE77_1788320073", + database="acme_corp", + collection="eng_slack", + depth=3, +) +``` + +```typescript TypeScript SDK +const subgraph = await client.context.subgraph({ + id: "slack_C0BE77_1788320073", + database: "acme_corp", + collection: "eng_slack", + depth: 3, +}); +``` + ```bash cURL curl -G 'https://api.hydradb.com/context/slack_C0BE77_1788320073/subgraph' \ -H "Authorization: Bearer " \ @@ -21,46 +39,31 @@ curl -G 'https://api.hydradb.com/context/slack_C0BE77_1788320073/subgraph' \ --data-urlencode "depth=3" ``` -```bash HydraDB CLI -hydradb subgraph slack_C0BE77_1788320073 --database acme_corp --collection eng_slack --depth 3 -hydradb --output json subgraph slack_C0BE77_1788320073 | jq '.sources[].source_id' -``` - -```json MCP tool -{ "name": "hydradb_subgraph", "arguments": { "id": "slack_C0BE77_1788320073", "depth": 3 } } -``` - - - 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 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 (Python and TypeScript SDK 2.1.7 use this form for every id). | ## Query parameters | 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`) | +| | Owning database. | +| | Collection scope. If omitted, the default collection is used. | | | Which graph the `id` belongs to. The two graphs are completely separate. (default=`"knowledge"`) | -| | Maximum traversal depth in hops. Range `1–10`. (default=`5`) | -| | Maximum number of members returned. Range `1–1000`. When this clips the traversal, `is_truncated` is `true`. (default=`200`) | +| | 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 -Every member except the start item 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_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. +Each item includes its distance from the starting source in `depth`. The starting source has `depth: 0`. -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. +- `discovered_via` identifies the source that led to this item. +- `discovered_relation` describes the connection, such as `same_thread`, `parent`, `child`, or a link declared at ingestion. @@ -105,7 +108,7 @@ Traversal is breadth-first, so `depth` on each member is its distance from the s ], "auxiliary_relations": [ { - "source": { "name": "saivenu", "type": "ACTOR", "namespace": "actors", "entity_id": "actor_saivenu", "identifier": "saivenu" }, + "source": { "name": "example_user", "type": "ACTOR", "namespace": "actors", "entity_id": "actor_example_user", "identifier": "example_user" }, "target": { "name": "C0BE77TPEU8:1788320073.073799", "type": "SOURCE", "namespace": "sources", "entity_id": "slack_C0BE77_1788320073", "identifier": null }, "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 } @@ -117,7 +120,7 @@ Traversal is breadth-first, so `depth` on each member is its distance from the s "is_truncated": false, "max_depth_reached": 1, "success": true, - "message": "Subgraph fetched successfully" + "message": "Successfully fetched source subgraph" }, "error": null, "meta": { @@ -127,28 +130,6 @@ Traversal is breadth-first, so `depth` on each member is its distance from the s } ``` -```json Unknown id -{ - "success": true, - "data": { - "seed_source_id": "no_such_item", - "sources": [], - "relations": [], - "auxiliary_relations": [], - "auxiliary_truncated": false, - "is_truncated": false, - "max_depth_reached": 0, - "success": true, - "message": "Subgraph fetched successfully" - }, - "error": null, - "meta": { - "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d", - "latency_ms": 6.1 - } -} -``` - ```json Failure { "success": false, @@ -168,30 +149,20 @@ 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 [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. -- **`relations[]`** are the item-level relations *among the members* (`relates_to`, `same_thread`, `parent_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 returns as `graph_context`. Those are a different read. - -## Some additional notes +- `sources[]` contains the starting source and connected items. Pass a `source_id` to [Inspect Context](/api-reference/v2/endpoint/fetch-content) to read its content. +- `relations[]` describes links between those sources, such as thread membership and parent-child relationships. +- `auxiliary_relations[]` includes related people, comments, and attachments. +- `is_truncated` and `auxiliary_truncated` indicate that response limits were reached. - - **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. - +## Errors -- **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. -- **Knowledge vs memory.** If the `id` belongs to a memory, set `type=memory`; the two graphs never connect to each other. -- **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. -- **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. +Common codes: `400 INVALID_INPUT` (missing `database` or `id`, `depth` outside `1` to `10`, or `max_sources` outside `1` to `1000`), `404 DATABASE_NOT_FOUND`, `500 INTERNAL_ERROR` (a transient graph read failure; retry the request). See [Error Responses](/api-reference/v2/error-responses) for the full list.
- - **Related Resources** +## 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) - - **Query with graph context:** [Query](/api-reference/v2/endpoint/query) with `graph_context: true` - - **Concepts:** [Concepts → Context Graphs](/essentials/v2/context-graphs) - +- **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 context:** [Query](/api-reference/v2/endpoint/query) with `graph_context: true` +- **Concepts:** [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..3397adc8 100644 --- a/api-reference/v2/endpoint/submit-feedback.mdx +++ b/api-reference/v2/endpoint/submit-feedback.mdx @@ -6,15 +6,15 @@ 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. +Set `source` to `user` or `agent` to identify who is submitting feedback. ## 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} +```text Query response {6} { "success": true, "data": { "chunks": [ /* ... */ ] }, @@ -27,11 +27,9 @@ 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 +40,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. @@ -53,17 +51,17 @@ What you already know the right answer to be. See [Ground truth](#ground-truth). The response you expected. Up to 8000 characters. - IDs of the sources that actually contain the answer. Up to 100. + IDs of the sources that actually contain the answer. Up to 100, each up to 256 characters. -`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 +69,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`. @@ -102,8 +100,12 @@ const result = await client.query({ query: "What is our refund policy?", }); +// Response fields are optional in the TypeScript types. +const requestId = result.meta?.requestId; +if (!requestId) throw new Error("query response has no request_id"); + await client.feedback.submit({ - requestId: result.meta.requestId, + requestId, feedback: "Returned the 2023 policy - the current one is in the Q3 handbook.", rating: "negative", source: "agent", @@ -184,25 +186,23 @@ 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 +```text "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. - -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. +- **`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. - -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. - +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. ```python Evaluation run +from hydra_db.core.api_error import ApiError + for case in eval_set: result = client.query(database="acme_corp", query=case.question) @@ -215,32 +215,34 @@ for case in eval_set: "source_ids": case.expected_sources, }, ) - except Exception: - continue # one unrecorded case should not abort the run + except ApiError as error: + if error.status_code != 429: + raise ``` -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. +This example skips feedback that exceeds the rate limit and continues the evaluation. Other errors stop the run so invalid credentials or malformed requests are visible. -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. +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. ## Errors +A successful submission returns `201`. + | 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` too long; unknown `rating`/`source`; `collection` without `database`; or any other limit in [Fields](#fields) exceeded | | `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` | -| `500` | Feedback could not be stored. Nothing was recorded; retrying is safe | +| `429` | Over the rate limit; see `Retry-After` | +| `500` | Feedback could not be recorded. Retrying is safe | -A `500` means the submission was **not** saved, so a retry cannot create a duplicate of something already stored. +Each submission is its own row, so the worst case of retrying after a `500` is a duplicate report. diff --git a/api-reference/v2/endpoint/sync-connector.mdx b/api-reference/v2/endpoint/sync-connector.mdx index 350da283..97542272 100644 --- a/api-reference/v2/endpoint/sync-connector.mdx +++ b/api-reference/v2/endpoint/sync-connector.mdx @@ -10,6 +10,14 @@ A `202` means the sync was started, not that it finished. If a sync is already r +```python Python SDK +run = client.connectors.sync("{connector_id}") +``` + +```typescript TypeScript SDK +const run = await client.connectors.sync({ id: "{connector_id}" }); +``` + ```bash cURL curl -X POST 'https://api.hydradb.com/connectors/{id}/sync' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -31,13 +39,13 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/sync' \ ## Errors -- `404`: no connector with this id in your workspace. +- `404`: no connector with this id in your organization. - `409`: the connector is paused, or it has no active resources to sync. [Resume](/api-reference/v2/endpoint/resume-connector) it, or [configure](/api-reference/v2/endpoint/configure-connector) resources first. - `429`: your plan's sync limit is reached for this period.
-## Related Resources +## Related resources - [Get Connector Status](/api-reference/v2/endpoint/get-connector-status): check that the sync succeeded - [Update Connector](/api-reference/v2/endpoint/update-connector): change how often scheduled syncs run diff --git a/api-reference/v2/endpoint/tenant-stats.mdx b/api-reference/v2/endpoint/tenant-stats.mdx index 153e604a..5469d433 100644 --- a/api-reference/v2/endpoint/tenant-stats.mdx +++ b/api-reference/v2/endpoint/tenant-stats.mdx @@ -5,14 +5,12 @@ description: "Retrieve usage statistics for a database." openapi: "api-reference/v2/openapi.json GET /databases/stats" --- -Get detailed metrics including storage growth and object counts. - -The response splits stats into two database-wide collections: `data.knowledge_collection` (Knowledge) and `data.memory_collection` (User memories), each with its own row count. Counts aggregate across all collections in the database. +Returns row counts for a database: one for the knowledge store (`data.knowledge_collection`) and one for memories (`data.memory_collection`), each summed across every collection in the database. ```python Python SDK -stats = client.databases.stats(database="your database id") +stats = client.databases.stats(database="my_first_database") ``` ```typescript TypeScript SDK @@ -88,20 +86,16 @@ curl -X GET 'https://api.hydradb.com/databases/stats?database=my_first_database' ## Behavior notes - - **`row_count` is chunks, not sources.** One ingested document typically becomes many chunks (e.g., a 30-page PDF can produce 100\+ rows). To count distinct sources, use [List Documents](/api-reference/v2/endpoint/list-documents) with `page_size=1` and read `total`. - +**`row_count` is chunks, not sources.** One ingested document typically becomes many chunks (e.g., a 30-page PDF can produce 100\+ rows). To count distinct sources, use [List Context](/api-reference/v2/endpoint/list-documents) with `page_size=1` and read `pagination.total`. -- **Both collections always exist:** Even if you've only ingested knowledge (or only memories), both collections are provisioned. Empty collections report `row_count: 0`. -- **Stats are eventually consistent:** Immediately after ingestion or deletion, counts may lag until background processing completes. Check [Ingestion Status](/api-reference/v2/endpoint/source-status) for recently ingested IDs before treating counts as final. +- **Both stores always report:** even if you've only ingested knowledge (or only memories), both counts are returned. An empty store reports `row_count: 0`. +- **Stats are eventually consistent:** immediately after ingestion or deletion, counts may lag until background processing completes. Check [Ingestion Status](/api-reference/v2/endpoint/source-status) for recently ingested IDs before treating counts as final.
- - **Related Resources** +## Related resources - - **List sources/memories:** [List Documents](/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/multi-tenant) - +- **List sources/memories:** [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:** [Multi-tenancy](/essentials/v2/multi-tenant) diff --git a/api-reference/v2/endpoint/tenant-status.mdx b/api-reference/v2/endpoint/tenant-status.mdx index ce1eb16c..3900c63b 100644 --- a/api-reference/v2/endpoint/tenant-status.mdx +++ b/api-reference/v2/endpoint/tenant-status.mdx @@ -4,12 +4,11 @@ description: "Check the readiness of a database's infrastructure." openapi: "api-reference/v2/openapi.json GET /databases/status" --- -Database creation is asynchronous, check if your database is ready before executing ingestion or any queries. +Database creation is asynchronous; check if your database is ready before executing ingestion or any queries. -The response includes `infra.ready_for_ingestion`, a convenience flag derived from the vectorstore fields below. For a single check, read that field. The underlying signals are: +The response includes `infra.ready_for_ingestion`, a convenience flag derived from the four signals below. For a single check, read that field. The underlying signals are: -- `infra.ready_for_ingestion === true` (derived - true once both vectorstores are provisioned) -- `infra.scheduler_status === true` (the background indexing scheduler) +- `infra.scheduler_status === true` (lifecycle provisioning has finished; `false` while the database is still being created, even if some collections already exist) - `infra.graph_status === true` (the graph layer) - `infra.vectorstore_status.knowledge === true` - `infra.vectorstore_status.memories === true` @@ -28,7 +27,7 @@ const response = await client.databases.status({ database: "my_first_database", }); -if (response.data?.infra.readyForIngestion) { +if (response.data?.infra?.readyForIngestion) { console.log("Database is ready."); } ``` @@ -74,7 +73,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", @@ -87,15 +86,11 @@ 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 documents. For distinct source or memory counts, use [List Documents](/api-reference/v2/endpoint/list-documents) and read `pagination.total` from the response. - +**Querying too early:** a `POST /query` sent before `ready_for_ingestion` is `true` returns `422 TENANT_INFRA_NOT_READY`.
- - **Related Resources** +## 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 81d9d111..62a2e6e4 100644 --- a/api-reference/v2/endpoint/tenants-overview.mdx +++ b/api-reference/v2/endpoint/tenants-overview.mdx @@ -1,6 +1,6 @@ --- -title: "Databases - Overview" -description: "Quick reference for all databases endpoints, their lifecycle, and when to call each." +title: "Databases: Overview" +description: "Create and manage isolated databases for your context." --- Databases are physically isolated spaces for storing context. In most integrations you create a database once, wait for provisioning, then ingest and query inside it. @@ -9,36 +9,35 @@ 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 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/stats`](/api-reference/v2/endpoint/tenant-stats) | `GET` | `databases.stats` | Row counts for a database | 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/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 | +| [`/databases/{database}/metadata-schema`](/api-reference/v2/endpoint/get-metadata-schema) | `GET` | TypeScript: `databases.getMetadataSchema`
Python: `databases.get_metadata_schema` | Read the metadata schema | No | ## Typical call sequence For a new database from scratch: 1. Create the database: `POST /databases` -2. Wait for provisioning: `GET /databases/status` until `scheduler_status`, `graph_status`, `vectorstore_status.knowledge`, and `vectorstore_status.memories` are all `true` -3. Ingest content: `POST /context/ingest` +2. Wait for provisioning: `GET /databases/status` until `infra.ready_for_ingestion` is `true` +3. Ingest context: `POST /context/ingest` 4. Wait for indexing: `GET /context/status` until sources are searchable 5. Retrieve context: `POST /query` 6. Inspect usage: `GET /databases/stats` -7. Delete: `DELETE /databases` For routine operations on an existing database: -```text -GET /databases -> list databases in the org -GET /databases/collections -> list active collections -GET /databases/stats -> check database health & growth -``` +- `GET /databases`: list databases in the org +- `GET /databases/collections`: list active collections +- `GET /databases/stats`: row counts for knowledge and memories ## 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 one enterprise customer. +- **Collection:** A group of context inside a database, created on the first write. Ingest and query using the same collection. A query can search several collections with `collections`. For shared company knowledge, start with one collection and use metadata filters and access control to limit results. +- **Database Metadata & Schema:** Structured fields defined at database creation to enable query-time filtering. You can add fields later with [Update Metadata Schema](/api-reference/v2/endpoint/update-metadata-schema). diff --git a/api-reference/v2/endpoint/test-webhook.mdx b/api-reference/v2/endpoint/test-webhook.mdx index 98c41a7e..28c59008 100644 --- a/api-reference/v2/endpoint/test-webhook.mdx +++ b/api-reference/v2/endpoint/test-webhook.mdx @@ -6,10 +6,28 @@ openapi: "api-reference/v2/openapi.json POST /webhooks/indexing/test" Sends a synthetic `indexing.status_changed` payload to your registered endpoint so you can confirm it is reachable and that your signature verification works. -The test payload carries `"test": true`, and is signed exactly like a real delivery when a signing secret is configured. Use it to check your verifier before relying on it in production. +The test payload carries `"test": true`, and is signed exactly like a real delivery when a signing secret is configured. Use it to check your verifier before relying on it in production. The response reports whether your endpoint accepted it (`delivered`) and the HTTP `status_code` it returned. If no webhook is registered, the call returns `404`. - - Test deliveries do not appear in the delivery history returned by [List Deliveries](/api-reference/v2/endpoint/list-webhook-deliveries). - +Test deliveries do not appear in the delivery history returned by [List Deliveries](/api-reference/v2/endpoint/list-webhook-deliveries). See [Webhooks](/essentials/v2/webhooks) for the full payload shape. + + + +```python Python SDK +result = client.webhooks.test() +print(result.data.delivered, result.data.status_code) +``` + +```typescript TypeScript SDK +const result = await client.webhooks.test(); +console.log(result.data?.delivered, result.data?.statusCode); +``` + +```bash cURL +curl -X POST 'https://api.hydradb.com/webhooks/indexing/test' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" +``` + + diff --git a/api-reference/v2/endpoint/update-connector-resource.mdx b/api-reference/v2/endpoint/update-connector-resource.mdx index bb3c34af..010824c6 100644 --- a/api-reference/v2/endpoint/update-connector-resource.mdx +++ b/api-reference/v2/endpoint/update-connector-resource.mdx @@ -4,15 +4,35 @@ description: "Change one resource's ingestion instructions or access rule, leavi openapi: "api-reference/v2/openapi.json PATCH /connectors/{id}/resources/{resource_id}" --- -Updates one configured resource in place. Send `custom_instructions`, `acl`, or both. At least one is required, and any field you omit is left unchanged. This is the safe way to edit a resource that is already syncing. +Updates one configured resource in place. Send `custom_instructions`, `acl`, or both. At least one is required, and any field you omit is left unchanged. `resource_id` in the path is the id from [List Connector Resources](/api-reference/v2/endpoint/connector-resources), for example a Slack channel id or a Supabase `schema.table` name. URL-encode it if it contains spaces or other reserved characters. -- **`custom_instructions`** sets instructions for documents from this resource only. They **replace** the connector-level instructions for this resource rather than adding to them. Send `""` to clear them so the resource uses the connector's instructions again. Up to 4,000 characters. They apply from the next sync; documents already synced are not processed again. See [Custom Ingestion Instructions](/essentials/v2/connectors#custom-ingestion-instructions). -- **`acl`** sets who can read this resource's objects, and applies to already-synced objects on the next query, with no re-sync. Use `["__public__"]` to open the resource to everyone and `[]` to allow nobody. For providers whose own permissions HydraDB reads, those permissions take over again at the next sync; your rule applies whenever the provider reports the resource as public or its permissions cannot be read. See [Access Control](/essentials/v2/access-control). +- `custom_instructions` replaces connector-level instructions for this resource. Maximum 4,000 characters. Send `""` to inherit the connector's instructions again. Changes affect future syncs. See [Custom Ingestion Instructions](/essentials/v2/connectors#custom-ingestion-instructions). +- `acl` changes access immediately, including for synced objects. Use `["__public__"]` for public access or `[]` to deny everyone. Provider permissions may replace this rule on the next sync. See [Access Control](/essentials/v2/access-control). +```python Python SDK +client.connectors.update_resource_acl( + "{connector_id}", + "{resource_id}", + request={ + "custom_instructions": "These threads are incident retros. Extract root cause, impact and owner." + }, +) +``` + +```typescript TypeScript SDK +await client.connectors.updateResourceAcl({ + id: "{connector_id}", + resourceId: "{resource_id}", + body: { + custom_instructions: "These threads are incident retros. Extract root cause, impact and owner.", + }, +}); +``` + ```bash cURL curl -X PATCH 'https://api.hydradb.com/connectors/{id}/resources/{resource_id}' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -43,11 +63,11 @@ The response includes `custom_instructions` and `acl` only when the request set ## Errors - `400`: neither field was sent, `custom_instructions` is over 4,000 characters, or the access rule is malformed. Nothing is changed. -- `404`: no connector with this id in your workspace, or no configured resource with this `resource_id` on it. +- `404`: no connector with this id in your organization, or no configured resource with this `resource_id` on it.
-## Related Resources +## Related resources - [Update Connector](/api-reference/v2/endpoint/update-connector): set the connector-level instructions these override - [Custom Ingestion Instructions](/essentials/v2/connectors#custom-ingestion-instructions): how connector and resource instructions combine diff --git a/api-reference/v2/endpoint/update-connector.mdx b/api-reference/v2/endpoint/update-connector.mdx index 930fc89d..2f70ca1e 100644 --- a/api-reference/v2/endpoint/update-connector.mdx +++ b/api-reference/v2/endpoint/update-connector.mdx @@ -4,14 +4,40 @@ description: "Change a connector's instructions, sync interval or credentials in openapi: "api-reference/v2/openapi.json PATCH /connectors/{id}" --- -Updates a connector in place. Every field is optional and only the fields you send change, so it is safe to call without re-sending anything else. Every field is checked before anything is saved, so if one is invalid, nothing changes. Credentials are saved before the other settings, so in the rare case of a server error after that, only the credentials may have changed; retrying the same request is safe. +Update a connector's instructions, sync interval, or credentials. Omitted fields keep their existing values. -- **`custom_instructions`** sets the connector-level ingestion instructions, used by every resource that has no instructions of its own. Send `""` to clear them. Up to 4,000 characters. They apply from the next sync; documents already synced are not processed again. See [Custom Ingestion Instructions](/essentials/v2/connectors#custom-ingestion-instructions). -- **`sync_interval_seconds`** sets how often scheduled syncs run. The allowed range depends on the provider and comes back in the response as `min_sync_interval_seconds` and `max_sync_interval_seconds`. Values outside it are rejected, and `0` resets to the provider default. The next sync is rescheduled right away, so a shorter interval takes effect immediately. -- **`credentials`** reconnects the connector, for example after a token expired or was revoked. Send the provider's full credential set, the same as on create. The connector keeps its id, resources and sync positions, and a reconnect-required state is cleared. +- HydraDB validates every field before saving. Invalid input changes nothing. +- Credentials are saved before other settings. If a server error occurs afterward, only the credentials may have changed. Retrying the same request is safe. + +- `custom_instructions` applies to resources without their own instructions. Maximum 4,000 characters. Send `""` to clear it. Changes affect future syncs. See [Custom Ingestion Instructions](/essentials/v2/connectors#custom-ingestion-instructions). +- `sync_interval_seconds` reschedules the next sync. Stay within the returned `min_sync_interval_seconds` and `max_sync_interval_seconds`, or send `0` for the provider default. +- `credentials` reconnects the existing connector. Send the provider's complete credential set. Resources and saved sync positions are preserved. + +Only these three fields can be updated. A connector's `name`, `database`, `collection` and `provider_account_scope` cannot be changed after creation. Omit them from the request; unsupported fields can cause a `400` response. +```python Python SDK +client.connectors.update( + "{connector_id}", + custom_instructions=( + "Aurora was renamed to Nimbus in January 2025; they are the same product. " + "Index all Aurora content under Nimbus." + ), + sync_interval_seconds=3600, +) +``` + +```typescript TypeScript SDK +await client.connectors.update({ + id: "{connector_id}", + customInstructions: + "Aurora was renamed to Nimbus in January 2025; they are the same product. " + + "Index all Aurora content under Nimbus.", + syncIntervalSeconds: 3600, +}); +``` + ```bash cURL curl -X PATCH 'https://api.hydradb.com/connectors/{id}' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -44,13 +70,13 @@ curl -X PATCH 'https://api.hydradb.com/connectors/{id}' \ ## Errors -- `400`: `custom_instructions` is over 4,000 characters, or `sync_interval_seconds` is outside the provider's range. Nothing is changed. -- `404`: no connector with this id in your workspace. +- `400`: the request includes an unsupported field, `custom_instructions` is over 4,000 characters, or `sync_interval_seconds` is outside the provider's range. Nothing is changed. +- `404`: no connector with this id in your organization. - `422`: `credentials` does not match the provider's `credential_schema`.
-## Related Resources +## Related resources - [Update Connector Resource](/api-reference/v2/endpoint/update-connector-resource): instructions for one resource - [Custom Ingestion Instructions](/essentials/v2/connectors#custom-ingestion-instructions): what instructions do and how they combine diff --git a/api-reference/v2/endpoint/update-metadata-schema.mdx b/api-reference/v2/endpoint/update-metadata-schema.mdx index f03c55ad..a3d66e09 100644 --- a/api-reference/v2/endpoint/update-metadata-schema.mdx +++ b/api-reference/v2/endpoint/update-metadata-schema.mdx @@ -6,14 +6,32 @@ openapi: "api-reference/v2/openapi.json PATCH /databases/{database}/metadata-sch import { Field } from "/snippets/field.jsx"; -Use this endpoint to add new fields to a database's `database_metadata_schema`. +Use this endpoint to add new fields to a database's `database_metadata_schema`. SDK methods: `client.databases.update_metadata_schema()` (Python) and `client.databases.updateMetadataSchema()` (TypeScript). [Get Metadata Schema](/api-reference/v2/endpoint/get-metadata-schema) returns the current schema in the same field shape. - - 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, change the type/flags of existing fields, or add dense or sparse search fields. +```python Python SDK +response = client.databases.update_metadata_schema( + "acme_corp", + add_fields=[ + {"name": "region", "data_type": "VARCHAR"}, + {"name": "priority", "data_type": "INT64"}, + ], +) +``` + +```typescript TypeScript SDK +const response = await client.databases.updateMetadataSchema({ + database: "acme_corp", + addFields: [ + { name: "region", dataType: "VARCHAR" }, + { name: "priority", dataType: "INT64" }, + ], +}); +``` + ```bash cURL curl -X PATCH 'https://api.hydradb.com/databases/acme_corp/metadata-schema' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -23,65 +41,16 @@ curl -X PATCH 'https://api.hydradb.com/databases/acme_corp/metadata-schema' \ "add_fields": [ { "name": "region", - "data_type": "VARCHAR", - "enable_match": true + "data_type": "VARCHAR" }, { - "name": "summary_label", - "data_type": "VARCHAR", - "enable_dense_embedding": true, - "enable_sparse_embedding": true + "name": "priority", + "data_type": "INT64" } ] }' ``` -```python Python -import requests - -response = requests.patch( - "https://api.hydradb.com/databases/acme_corp/metadata-schema", - headers={ - "Authorization": f"Bearer {HYDRA_DB_API_KEY}", - "API-Version": "2", - "Content-Type": "application/json", - }, - 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, - }, - ] - }, -) -``` - -```typescript TypeScript -const response = await fetch("https://api.hydradb.com/databases/acme_corp/metadata-schema", { - method: "PATCH", - headers: { - Authorization: `Bearer ${process.env.HYDRA_DB_API_KEY}`, - "API-Version": "2", - "Content-Type": "application/json", - }, - 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, - }, - ], - }), -}); -``` - ## Request @@ -90,48 +59,42 @@ const response = await fetch("https://api.hydradb.com/databases/acme_corp/metada | Name | Description | | --- | --- | -| | Database whose metadata schema should be extended. Formerly `tenant_id`; the `tenant_id` alias is still accepted (deprecated). | +| | Database whose metadata schema should be extended. | ### Body | Name | Description | | --- | --- | -| | New metadata schema fields to append. Must contain at least one field. | +| | New metadata schema fields to append. Must contain at least one field. A field that already exists with an identical definition is skipped, so you can resend your full field list. | Each `add_fields[]` item uses the same field shape as `database_metadata_schema` on [Create Database](/api-reference/v2/endpoint/create-tenant): | 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/underscores (up to 255 characters), 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 a dense semantic-search lane for a `VARCHAR` metadata field. | -| | Adds a sparse/BM25 search lane for a `VARCHAR` metadata field. | -| | Backward-compatible shorthand for `enable_match: true`. Prefer `enable_match`. | +| | Rejected with `400` on a new field. Dense semantic-search fields can only be declared at [database creation](/api-reference/v2/endpoint/create-tenant). | +| | Rejected with `400` on a new field. Sparse keyword-search fields can only be declared at database creation. | ## Rules -- Additions only. -- Existing field names cannot be reused, case-insensitively. -- Existing fields cannot be deleted or changed. -- Total custom database metadata fields cannot exceed 32. +- Existing field names cannot be reused with a different definition, case-insensitively. A field that differs from the existing one in type, `max_length`, or flags returns `409`, as does one name declared twice in a request with different definitions. Resending a field with its identical definition is a no-op. +- Total custom database metadata fields cannot exceed 32. Skipped fields do not count toward the new total. - Reserved names such as `source_id`, `chunk_id`, `metadata`, and `document_metadata` are rejected. -- Dense/sparse embedding flags are only valid on `VARCHAR` fields. -- MongoDB indexes for `enable_match` fields are created before the merged schema is persisted. - - - This endpoint persists the updated schema and MongoDB filter indexes. It does not yet alter/backfill existing Milvus collections 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. - +- Resending an existing field that already has dense or sparse flags is a no-op. ## Response +A success returns `database`, the deprecated `tenant_id`, and `added_fields` directly, without the `success`/`data` envelope. `added_fields` lists only the fields this call added, so an empty list means the database already had everything you sent. Errors use the standard [error envelope](/api-reference/v2/error-responses). + ```json Success { "database": "acme_corp", - "added_fields": ["region", "summary_label"] + "tenant_id": "acme_corp", + "added_fields": ["region", "priority"] } ``` @@ -140,8 +103,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 +119,16 @@ 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. | -| `404` | Database not found. | -| `409` | Field already exists or the update conflicts with stored database mapping/schema state. | -| `500` | Backend persistence or index creation failed. | +| `400` | Invalid request body (`INVALID_INPUT`), empty `add_fields`, invalid field name/type, `ARRAY`, too many fields, or an embedding flag on a new field. | +| `404` | Database not found (`DATABASE_NOT_FOUND`). | +| `409` | A field conflicts with an existing field or with another entry in the same request, or the database changed during the request (retry this last case). | +| `500` | Saving the schema failed. Retry. | + +Apart from a malformed body and a missing database, this endpoint currently returns `error.code: "INTERNAL_ERROR"` for `400`, `409`, and `500` alike. Branch on the HTTP status and read `error.message`, which names the field and the rule it broke. ## Related - [Create Database](/api-reference/v2/endpoint/create-tenant) -- [Scoping using metadata](/essentials/v2/metadata) +- [Metadata and Filters](/essentials/v2/metadata) - [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 2ab9c162..e8546f85 100644 --- a/api-reference/v2/endpoint/update-source-metadata.mdx +++ b/api-reference/v2/endpoint/update-source-metadata.mdx @@ -1,23 +1,41 @@ --- title: "Update Source Metadata" -description: "Merge tenant metadata and additional metadata for one existing source without re-ingesting its content." +description: "Merge database metadata and additional metadata into one existing source, without re-ingesting it." 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. +Update an existing source's metadata without re-ingesting its content. Changes also apply to the indexed chunks used by Query and List Context. See [Metadata and Filters](/essentials/v2/metadata#6-update-metadata-without-re-ingesting) for the metadata workflow. ```http PATCH /context/{id}/metadata ``` - - The legacy route `PATCH /context/sources/{source_id}/metadata` still works but is deprecated - migrate to the route above. Both dispatch to the same handler; `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. `source_id` and `id` identify the same source. +```python Python SDK +response = client.context.update_source_metadata( + "policy_main", + database="acme_corp", + collection="team_docs", + database_metadata={"department": "legal", "priority": 7}, + additional_metadata={"author": "Legal Team", "doc_version": 3}, +) +``` + +```typescript TypeScript SDK +const response = await client.context.updateSourceMetadata({ + id: "policy_main", + database: "acme_corp", + collection: "team_docs", + databaseMetadata: { department: "legal", priority: 7 }, + additionalMetadata: { author: "Legal Team", doc_version: 3 }, +}); +``` + ```bash cURL curl -X PATCH 'https://api.hydradb.com/context/policy_main/metadata' \ -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ @@ -37,54 +55,6 @@ curl -X PATCH 'https://api.hydradb.com/context/policy_main/metadata' \ }' ``` -```python Python -import requests - -response = requests.patch( - "https://api.hydradb.com/context/policy_main/metadata", - headers={ - "Authorization": f"Bearer {HYDRA_DB_API_KEY}", - "API-Version": "2", - "Content-Type": "application/json", - }, - json={ - "database": "acme_corp", - "collection": "team_docs", - "database_metadata": { - "department": "legal", - "priority": 7, - }, - "additional_metadata": { - "author": "Legal Team", - "doc_version": 3, - }, - }, -) -``` - -```typescript TypeScript -const response = await fetch("https://api.hydradb.com/context/policy_main/metadata", { - method: "PATCH", - headers: { - Authorization: `Bearer ${process.env.HYDRA_DB_API_KEY}`, - "API-Version": "2", - "Content-Type": "application/json", - }, - body: JSON.stringify({ - database: "acme_corp", - collection: "team_docs", - database_metadata: { - department: "legal", - priority: 7, - }, - additional_metadata: { - author: "Legal Team", - doc_version: 3, - }, - }), -}); -``` - ## Request @@ -99,27 +69,27 @@ const response = await fetch("https://api.hydradb.com/context/policy_main/metada | Name | Description | | --- | --- | -| | 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`) | +| | Owning database. | +| | Collection that contains the source. This endpoint does not default it. | +| | Schema-backed metadata fields to merge into the source's `metadata`. Keys must satisfy the database metadata schema when one exists. | | | Free-form metadata fields to merge into the source's `additional_metadata`. | +| | Replaces the source's access-control list, so send the complete new list. `[]` or `null` revokes access for everyone. Omit it to keep the stored list. 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 shorter `metadata` field used by ingestion/list examples is not accepted in this PATCH body. `document_metadata` is also not accepted; use `additional_metadata`. - +This endpoint accepts `database_metadata` and `additional_metadata`. It does not accept `metadata` or `document_metadata`. -## Behavior + -- The update is a **merge/upsert**: - - keys present in the request are inserted or overwritten - - keys omitted from the request are preserved +## How updates apply + +- Metadata keys included in the request are added or overwritten. Omitted keys keep their existing values. +- Sending `acl` replaces the entire stored access-control list. - 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 refreshes the relevant vector store metadata search lane. -- If the edited fields are `enable_match`-only, the edit remains MongoDB-only and `vector_sync_required` is `false`. +- If an edited database metadata field has `enable_dense_embedding` or `enable_sparse_embedding`, HydraDB synchronously refreshes the relevant metadata search index. +- If no edited field has an embedding flag, no vector refresh is needed and `vector_sync_required` is `false`. ## Response @@ -130,14 +100,12 @@ At least one of `database_metadata` or `additional_metadata` is required. "success": true, "data": { "id": "policy_main", - "tenant_id": "acme_corp", - "sub_tenant_id": "team_docs", + "database": "acme_corp", + "collection": "team_docs", "updated": true, "database_metadata_keys": ["department", "priority"], - "tenant_metadata_keys": ["department", "priority"], "additional_metadata_keys": ["author", "doc_version"], "vector_sync_required": false, - "milvus_sync_required": false, "chunk_rows_matched": 4, "chunk_rows_modified": 4 }, @@ -154,18 +122,14 @@ At least one of `database_metadata` or `additional_metadata` is required. "success": true, "data": { "id": "policy_main", - "tenant_id": "acme_corp", - "sub_tenant_id": "team_docs", + "database": "acme_corp", + "collection": "team_docs", "updated": true, "database_metadata_keys": ["summary_label"], - "tenant_metadata_keys": ["summary_label"], "additional_metadata_keys": [], "vector_sync_required": true, "vector_synced": true, "vector_rows_synced": 4, - "milvus_sync_required": true, - "milvus_synced": true, - "milvus_rows_synced": 4, "chunk_rows_matched": 4, "chunk_rows_modified": 4 }, @@ -182,8 +146,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", @@ -197,50 +161,36 @@ 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. | +| | The database you named. | +| | Collection that contained the source. | | | `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. | -| | Present when sync was required. `true` means the sync completed. | +| | `true` when at least one changed database metadata field has dense/sparse embedding enabled. | +| | Present (`true`) when a required sync completed. Absent when no sync was required or the sync failed. | | | Number of chunk rows synced to the vector store when sync was required. | -| | 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. | -| | Number of MongoDB chunk rows matched by the source update. | -| | Number of MongoDB chunk rows modified by the source update. | +| | Present when a required sync failed after the metadata was saved. Retry the same edit. | +| | Present and `true` when the edit replaced the source's `acl`. | +| | Present and `true` when an `acl` edit also reached the vector store. If absent after an `acl` edit, the new list is still enforced, but an added principal may miss this source in results until it is re-indexed. | +| | Present when the edit was saved in one store but a later write failed. Retry the same edit. | +| | Number of stored chunk records matched by the source update. | +| | Number of stored chunk records modified by the source update. | ## Validation and errors | 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`, empty metadata payload, `document_metadata` supplied, 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 written to MongoDB but dense/sparse vector store sync failed. Retry the same idempotent edit to converge. | +| `500` | The edit failed. Retry the same idempotent edit to converge. | + +If the metadata is saved but the dense/sparse vector store sync fails, the call still returns `200`, without `vector_synced` and with `vector_sync_error` explaining why. Retry the same idempotent edit to converge. ### 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 -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 - 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 -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 -reports both numbers: +`database_metadata` is capped at **16 KiB**. `additional_metadata` is capped at **1 KiB**. See [metadata size limits](/essentials/v2/metadata#stored-metadata) for how HydraDB measures these caps. + +Limits apply to the request payload, before merging with stored metadata. An oversized request returns `400` with the actual and maximum sizes: ```json { @@ -253,11 +203,9 @@ reports both numbers: } ``` -See [Scoping using metadata → Size limits](/essentials/v2/metadata#size-limits). - ## Related -- [Scoping using metadata](/essentials/v2/metadata) +- [Metadata and Filters](/essentials/v2/metadata) - [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/endpoint/webhooks-overview.mdx b/api-reference/v2/endpoint/webhooks-overview.mdx new file mode 100644 index 00000000..484def40 --- /dev/null +++ b/api-reference/v2/endpoint/webhooks-overview.mdx @@ -0,0 +1,48 @@ +--- +title: "Webhooks: Overview" +description: "Receive indexing updates and track webhook deliveries." +--- + +A webhook is an HTTP `POST` that HydraDB sends to your endpoint when ingested content reaches a terminal indexing state, `completed` or `errored`, so you do not have to poll [Ingestion Status](/api-reference/v2/endpoint/source-status). One webhook is registered per organization, for the `indexing.status_changed` event. The [Webhooks guide](/essentials/v2/webhooks) covers the payload, signature verification, and receiver examples. + +## Endpoints + +Set up the webhook: + +- [Register Webhook](/api-reference/v2/endpoint/register-webhook): `POST /webhooks/indexing` registers your endpoint, or replaces the current registration. Send `generate_signing_secret: true` to get a signing secret in the same call. +- [Send Test Delivery](/api-reference/v2/endpoint/test-webhook): `POST /webhooks/indexing/test` sends a synthetic, signed payload to confirm your endpoint and your signature check work. +- [Get Webhook](/api-reference/v2/endpoint/get-webhook): `GET /webhooks/indexing` reads the current registration and whether a signing secret is set. + +Track deliveries: + +- [List Deliveries](/api-reference/v2/endpoint/list-webhook-deliveries): `GET /webhooks/indexing/deliveries` lists delivery attempts, most recent first. Filter by `status` to find failures. +- [Get Delivery](/api-reference/v2/endpoint/get-webhook-delivery): `GET /webhooks/indexing/deliveries/{delivery_id}` reads one delivery, using the id from the `X-HydraDB-Delivery-ID` header. +- [Retry Delivery](/api-reference/v2/endpoint/retry-webhook-delivery): `POST /webhooks/indexing/deliveries/{delivery_id}/retry` queues a `failed` or `permanently_failed` delivery for another attempt. + +Manage signing or remove the webhook: + +- [Set Signing Secret](/api-reference/v2/endpoint/set-webhook-signing-secret): `POST /webhooks/indexing/signing-secret` generates a new signing secret, or stores one you supply. The secret is returned once and takes effect immediately. If you supply the new secret, configure your receiver to accept it before this call. +- [Disable Signing](/api-reference/v2/endpoint/disable-webhook-signing): `DELETE /webhooks/indexing/signing-secret` turns signing off. +- [Delete Webhook](/api-reference/v2/endpoint/delete-webhook): `DELETE /webhooks/indexing` removes the registration and its signing secret. + +## Typical call sequence + +1. `POST /webhooks/indexing` with your HTTPS endpoint and `generate_signing_secret: true`. Store the secret it returns. +2. `POST /webhooks/indexing/test` to check your receiver accepts the delivery and verifies the signature. +3. Ingest content as usual. Each item that finishes indexing produces one delivery. +4. `GET /webhooks/indexing/deliveries?status=failed` to find deliveries your endpoint rejected, then retry them once the problem is fixed. + +## Delivery and retries + +- Each delivery contains the event body and an `X-HydraDB-Delivery-ID` header. Signed deliveries also include `X-HydraDB-Signature`. +- Failed deliveries retry with exponential backoff. HydraDB Cloud marks a delivery `permanently_failed` after 16 attempts or 48 hours after the first failure, whichever comes first. +- Automatic and manual retries use your current signing secret. +- Test deliveries do not appear in the delivery history. + +Your endpoint must be reachable over public HTTPS; localhost and private network addresses are rejected. + +## Related resources + +- [Webhooks guide](/essentials/v2/webhooks): payload fields, signature verification, receiver examples, and secret rotation +- [Ingestion Status](/api-reference/v2/endpoint/source-status): polling instead of a webhook +- [Ingest Context](/api-reference/v2/endpoint/ingest-context): the ingestion that produces deliveries diff --git a/api-reference/v2/error-responses.mdx b/api-reference/v2/error-responses.mdx index 9be2d49c..29d3aa80 100644 --- a/api-reference/v2/error-responses.mdx +++ b/api-reference/v2/error-responses.mdx @@ -3,9 +3,9 @@ title: "Error Responses" description: "Response envelope, HTTP status codes, error codes, and retry patterns." --- -### Response envelope +## Response envelope -HydraDB core endpoints (`/databases`, `/context/*`, and `/query`) use the same top-level envelope for successful and failed requests. Webhook management endpoints (`/webhooks/indexing*`) return their documented response object directly and do not include this envelope. +HydraDB core endpoints (`/databases`, `/context/*`, `/query`, `/feedback`, and `/webhooks/indexing*`) use the same top-level envelope for successful and failed requests. Connector endpoints (`/connectors*`) and `PATCH /databases/{database}/metadata-schema` return their success body without this envelope, but their errors use it too. ```json { @@ -22,20 +22,16 @@ 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. | +| `data` | Usually `null`. Some `DELETE /context` errors include per-ID results. | | `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. - ## HTTP status codes @@ -43,10 +39,13 @@ 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 | +| `402` | Your plan does not allow the request, for example one connector too many | No. Upgrade | +| `403` | Authenticated, but not permitted for the resource, or the plan's database limit is reached | No | | `404` | Database, source, memory, or related resource was not found | No | -| `409` | Conflict, usually an existing database or context item ID | Usually no | -| `422` | Well-formed request that failed validation | No | +| `409` | Conflict: the database already exists, a source is still indexing when you delete it, or a schema field conflicts | Only `SOURCE_PROCESSING`, after `Retry-After` | +| `413` | Request body or upload too large | No | +| `415` | Unsupported `Content-Type`, such as a `POST /context/ingest` body that is neither a multipart form nor JSON | No | +| `422` | Well-formed request that failed validation, or a query sent before the database finished provisioning | Only `TENANT_INFRA_NOT_READY`, once the database is ready | | `429` | Rate limit exceeded | Yes, with backoff | | `500` | Internal server error | Yes, with backoff | | `503` | Temporary service unavailability | Yes, with backoff | @@ -55,106 +54,63 @@ Use `error.code` for branching and log `meta.request_id` for every failed reques | Code | Typical status | Meaning | |---|---|---| -| `INVALID_PARAMETERS` | `400` | A required parameter is missing, malformed, or mutually incompatible with another parameter. | +| `INVALID_INPUT` | `400` | The default for a bad request: a required parameter is missing, malformed, or mutually incompatible with another parameter. Also used for `413` and `415`. | | `UNAUTHORIZED` | `401` | The `Authorization: Bearer ` header is missing or invalid. | -| `FORBIDDEN` | `403` | The API key is valid but does not have access to the requested resource, or the account/plan limit prevents the operation. | +| `PAYMENT_REQUIRED` | `402` | A plan limit refused the write, for example the connector count. | +| `FREE_PLAN_DEPRECATED` | `402` | The organization is on the deprecated Free plan. The message and `detail.upgrade_url` carry the upgrade link. | +| `FORBIDDEN` | `403` | The API key is valid but does not have access to the requested route or resource, or the plan's database limit prevents the operation. | | `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. | -| `SOURCE_NOT_FOUND` | `404` | The requested source or memory ID does not exist in the selected database/collection. | -| `VALIDATION_ERROR` | `422` | The request shape was valid JSON/form data, but one or more fields failed semantic validation. | +| `NOT_FOUND` | `404` | The requested resource does not exist, for example a source ID in `DELETE /context`. | +| `SOURCE_PROCESSING` | `409` | `DELETE /context` hit a source that is still indexing. Retry after `Retry-After`. | +| `VALIDATION_ERROR` | `400` or `422` | A field has an invalid value. Query filter errors return `400`. Invalid connector credentials return `422`. | +| `TENANT_INFRA_NOT_READY` | `422` | `POST /query` reached a database that is still provisioning. Poll [Database Status](/api-reference/v2/endpoint/tenant-status) until `ready_for_ingestion` is `true`, then retry. | +| `CORPUS_TYPE_UNSUPPORTED` | `400` | The `type` value does not fit this endpoint or database, for example `type: "all"` on ingest. | | `RATE_LIMITED` | `429` | The API key exceeded its current rate limit. | -| `INTERNAL_ERROR` | `500` | HydraDB hit an unexpected server-side error. | +| `INTERNAL_ERROR` | `500` | HydraDB hit an unexpected server-side error. A rare auth-layer failure reports `INTERNAL_SERVER_ERROR` instead. | | `SERVICE_UNAVAILABLE` | `503` | A dependency is temporarily unavailable or the service is under load. | - Endpoint pages list the most common codes for that operation. New codes may be added over time, so clients should handle unknown `error.code` values gracefully. - - -**Missing database returns `DATABASE_NOT_FOUND`.** A request for a database that does not exist returns `DATABASE_NOT_FOUND` on both the canonical `/databases` routes and the deprecated `/tenants` routes. The deprecated `/tenants` routes otherwise keep their pre-rename error contract for backward compatibility: a duplicate on `POST /tenants` returns `INVALID_INPUT` (not `DATABASE_ALREADY_EXISTS`), whereas the canonical `POST /databases` returns `DATABASE_ALREADY_EXISTS` as shown above. The HTTP status is identical on both. See [Migrating from `tenant_id` and `sub_tenant_id`](/essentials/v2/multi-tenant#7-migrating-from-the-legacy-tenant-and-sub-tenant-fields). - +`PATCH /databases/{database}/metadata-schema` currently returns `INTERNAL_ERROR` for its `400` and `409` errors too, so branch on the HTTP status there. + +For legacy route and field behavior, see [Migrating from tenant fields](/essentials/v2/multi-tenant#7-migrating-from-the-legacy-tenant-and-sub-tenant-fields). ## 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. -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 | +| Code | Meaning | What to do | |---|---|---| -| `E1002` | The file format is not supported. Returned in the [`POST /context/ingest`](/api-reference/v2/endpoint/ingest-context) response itself, per file, before anything is queued. User message: *"This file format isn't supported. Please upload a PDF, Office document (Word, Excel, PowerPoint), image, CSV or text file."* | **Terminal** (not retried) | -| `E2002` | No text could be extracted from the file: a blank scanned page, or a photo or image with no text in it. The file itself is valid. User message: *"No text content could be extracted from your file. Please ensure it isn't empty or image-only."* | **Terminal** (the same file returns the same result; upload a version with text, or send the text directly) | -| `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) | +| `E1002` | Unsupported file format. Read this code in the upload response. The file is not queued. | Upload a supported format. | +| `E2002` | The file contains no readable text. | Upload a version with text. | +| `E6001` | Processed data could not be stored. | Retried automatically. | - -`E1002` is the one ingestion code that does **not** follow the polling advice above. The file is rejected at upload, so it never enters the pipeline and never gets a status record. Polling [`/context/status`](/api-reference/v2/endpoint/source-status) for it returns `FILE_NOT_FOUND`, not `E1002`. Read `error_code` on each item in the upload response instead. See [Supported file formats](/api-reference/v2/endpoint/ingest-context#supported-file-formats) for what is accepted, and note that one rejected file does not affect the other files in the same request. - - - -`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. - +Files rejected with `E1002` have no ingestion status record. Handle this code from the upload response. See [Supported file formats](/api-reference/v2/endpoint/ingest-context#supported-file-formats). ## Retry pattern -Retry only transient failures: `429`, `500`, and `503`. Use exponential backoff with jitter and keep retries bounded. - - -```typescript TypeScript SDK -import { HydraDBError } from "@hydradb/sdk"; - -async function withRetry( - operation: () => Promise, - maxRetries = 3 -): Promise { - for (let attempt = 0; attempt <= maxRetries; attempt++) { - try { - return await operation(); - } catch (error) { - if (!(error instanceof HydraDBError)) throw error; - - const retryable = [429, 500, 503].includes(error.statusCode); - if (!retryable || attempt === maxRetries) throw error; - - const baseDelayMs = 2 ** attempt * 1000; - const jitterMs = Math.floor(Math.random() * 250); - await new Promise((resolve) => setTimeout(resolve, baseDelayMs + jitterMs)); - } - } +Retry only transient failures: `429`, `500`, and `503`. Use exponential backoff with jitter and keep retries bounded. Two other codes clear on their own, so wait instead of backing off blindly: `409 SOURCE_PROCESSING` (wait for `Retry-After`) and `422 TENANT_INFRA_NOT_READY` (wait until the database is ready). - throw new Error("unreachable"); -} +**The SDKs already retry:** both retry `408`, `429`, and `5xx` responses twice with exponential backoff before raising, and the Python SDK also retries `409`. Raise the count with `request_options={"max_retries": 5}` on a Python call, or `maxRetries` on the TypeScript client or call. The SDK examples below increase that limit. The raw HTTP example implements retries directly. Retry writes only when repeating the operation is safe. -const result = await withRetry(() => - client.query({ - database: "my_first_database", - query: "What are the pricing tiers?", - type: "knowledge", - }) -); -``` + ```python Python SDK -import random -import time - -from hydra_db.core.api_error import ApiError - -def with_retry(operation, max_retries=3): - for attempt in range(max_retries + 1): - try: - return operation() - except ApiError as exc: - retryable = exc.status_code in (429, 500, 503) - if not retryable or attempt == max_retries: - raise - - delay = (2 ** attempt) + random.uniform(0, 0.25) - time.sleep(delay) - -result = with_retry(lambda: client.query( +result = client.query( database="my_first_database", query="What are the pricing tiers?", type="knowledge", -)) + request_options={"max_retries": 5}, +) +``` +```typescript TypeScript SDK +const result = await client.query({ + database: "my_first_database", + query: "What are the pricing tiers?", + type: "knowledge", +}, { maxRetries: 5 }); ``` ```typescript fetch async function hydraRequest(url: string, options: RequestInit, maxRetries = 3) { @@ -189,8 +145,12 @@ try { }); } catch (error) { if (error instanceof HydraDBError) { - const code = error.body?.error?.code; - const requestId = error.body?.meta?.request_id; + // error.body is typed `unknown`; it holds the raw snake_case error envelope. + const body = error.body as + | { error?: { code?: string }; meta?: { request_id?: string } } + | undefined; + const code = body?.error?.code; + const requestId = body?.meta?.request_id; if (code === "DATABASE_NOT_FOUND") { // Create or select a valid database before ingesting. @@ -236,7 +196,7 @@ except ApiError as exc: Send exactly one `Authorization` header: -```bash +```http Authorization: Bearer ``` @@ -244,7 +204,7 @@ Also send `API-Version: 2` on raw HTTP requests. The official SDKs set the versi ### Database not found after creation -Database creation is asynchronous. After `POST /databases`, poll [`GET /databases/status`](/api-reference/v2/endpoint/tenant-status) until `infra.scheduler_status`, `infra.graph_status`, `infra.vectorstore_status.knowledge`, and `infra.vectorstore_status.memories` are all `true`. +Database creation is asynchronous. After `POST /databases`, poll [`GET /databases/status`](/api-reference/v2/endpoint/tenant-status) until `infra.scheduler_status`, `infra.graph_status`, `infra.vectorstore_status.knowledge`, and `infra.vectorstore_status.memories` are all `true` (`infra.ready_for_ingestion` combines them). A query sent earlier returns `422 TENANT_INFRA_NOT_READY`. ### Ingestion validation errors @@ -253,7 +213,7 @@ Common causes: - `document_metadata` length does not match the `documents` array length. - `app_knowledge`, `memories`, or `document_metadata` was sent as an object instead of a JSON-stringified multipart field. - A memory item has neither `text` nor `user_assistant_pairs`. -- A typed `tenant_metadata` value does not match the database metadata schema. +- A typed metadata value does not match the database metadata schema. ### Empty query results @@ -262,10 +222,10 @@ 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). - `metadata_filters` may be too restrictive or may target the wrong metadata namespace. - The query may be scoped to the wrong `database` or `collection` (formerly `tenant_id` / `sub_tenant_id`). -- The `type` value may exclude the collection you need. Use `type: "all"` when combining knowledge and memories in `POST /query`. +- The `type` value may exclude the store you need. Use `type: "all"` when combining knowledge and memories in `POST /query`. ## 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 f46336a6..04a7e63d 100644 --- a/api-reference/v2/index.mdx +++ b/api-reference/v2/index.mdx @@ -1,38 +1,18 @@ --- title: "API Reference" -description: "Single reference to all HydraDB endpoints" +description: "Requests, responses, and SDK examples for the HydraDB API." --- -## Quick links +Use this reference to look up request fields and response formats. New to HydraDB? The [Quickstart](/get-started/v2/quickstart) gets you to a first query in five minutes, and [Core Concepts](/get-started/v2/core-concepts) explains databases, context, and queries. AI agents can start from the [Agent Integration Guide](/AGENTS) and the [v2 OpenAPI spec](/api-reference/v2/openapi.json). -- **New to HydraDB?** Start with the [Quickstart](/get-started/v2/quickstart) -- **Prefer SDKs?** See [SDKs - 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) -- **For AI agents:** See the [Agent Integration Guide](/AGENTS) and [v2 OpenAPI spec](/api-reference/v2/openapi.json) +## The four calls -## Endpoint groups +Create a database, ingest context, wait for indexing, then query it. -| 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 knowledge or memories | Every time data flows into HydraDB - document uploads, app sources, user memories, 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 - -| 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/multi-tenant) | -| [Knowledge](/essentials/v2/knowledge) | Shared source material such as PDFs, docs, app pages, tickets, Slack threads, or webpages. | Use `type=knowledge` when many users or agents should query the same content. | -| [Memory](/essentials/v2/memories) | User-specific context such as preferences, conversation history, notes, and inferred traits. | Use `type=memory` when the content should personalize answers for a specific user or collection. | -| `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. | -| `document_metadata` | JSON-stringified per-document metadata array sent during file ingestion. | Use it to attach source IDs, titles, schema-backed `metadata`, free-form `additional_metadata`, or forceful relations to each uploaded document. | -| `ids` | IDs returned by ingestion or visible from `/context/list`. | Use them when polling processing status, inspecting content, listing a specific subset, deleting sources, or inspecting relations. | - -## End-to-end lifecycle +1. [Create Database](/api-reference/v2/endpoint/create-tenant): `POST /databases`. Isolates context for a customer, environment, or product. Creation runs in the background, so poll [Database Status](/api-reference/v2/endpoint/tenant-status) until `ready_for_ingestion` is `true`. +2. [Ingest Context](/api-reference/v2/endpoint/ingest-context): `POST /context/ingest`. Files, app sources, or memories, in one multipart request. +3. [Ingestion Status](/api-reference/v2/endpoint/source-status): `GET /context/status`. Poll the returned ids until `indexing_status` is `completed`, or [register a webhook](/essentials/v2/webhooks) instead. +4. [Query](/api-reference/v2/endpoint/query): `POST /query`. Retrieve the context your model needs, from knowledge, memories, or both. ```mermaid flowchart LR @@ -56,62 +36,92 @@ flowchart LR style G fill:#0f172a,stroke:#334155,stroke-width:2px,color:#f8fafc ``` -## SDKs - -HydraDB publishes official SDKs for Python and TypeScript/Node. They wrap every endpoint in this reference with typed methods and IDE autocomplete. - -| Language | Package | Install | -|---|---|---| -| **Python** | [`hydradb-sdk` on PyPI](https://pypi.org/project/hydradb-sdk/) | `pip install hydradb-sdk` | -| **TypeScript / Node** | [`@hydradb/sdk` on npm](https://www.npmjs.com/package/@hydradb/sdk) | `npm install @hydradb/sdk` | - -**Quick init:** - - - -```python Python -import os -from hydra_db import HydraDB, AsyncHydraDB - -client = HydraDB(token=os.environ["HYDRA_DB_API_KEY"]) -async_client = AsyncHydraDB(token=os.environ["HYDRA_DB_API_KEY"]) -``` - -```typescript TypeScript -import { HydraDBClient } from "@hydradb/sdk"; - -const client = new HydraDBClient({ - token: process.env.HYDRA_DB_API_KEY, -}); -``` - - - -SDK methods mirror the API: `client..()` maps to the corresponding endpoint. The SDKs are generated from the OpenAPI contract and set `API-Version: 2` automatically. - -## Full endpoint inventory - -| Endpoint | Method | SDK method | Purpose | Use when | -|---|---|---|---|---| -| [`/databases`](/api-reference/v2/endpoint/create-tenant) | `POST` | `databases.create` | Create a database | You are setting up a new isolated workspace and optional metadata schema. | -| [`/databases/{database}/metadata-schema`](/api-reference/v2/endpoint/update-metadata-schema) | `PATCH` | REST | Add metadata schema fields | You need to add filterable metadata fields after database creation. | -| [`/databases`](/api-reference/v2/endpoint/list-tenants) | `GET` | `databases.list` | List databases | You need to discover database IDs available to the current API key. | -| [`/databases`](/api-reference/v2/endpoint/delete-tenant) | `DELETE` | `databases.delete` | Delete a database | You need to permanently remove a workspace and its data. | -| [`/databases/status`](/api-reference/v2/endpoint/tenant-status) | `GET` | `databases.status` | Check provisioning readiness | You just created a database and need to wait before ingesting data. | -| [`/databases/collections`](/api-reference/v2/endpoint/list-sub-tenants) | `GET` | `databases.collections` / `databases.collections` | List active collections | You partition data by user, team, customer, or account and need to inspect those partitions. | -| [`/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 knowledge or memories | You are uploading documents, app sources, or user memories. | -| [`/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` | Inspect original source content or presigned URL | You need to display or inspect the original ingested content. | -| [`/context/list`](/api-reference/v2/endpoint/list-documents) | `POST` | `context.list` | Browse knowledge or memories | You need pagination, filters, field projection, or a specific subset by `ids`. | -| [`/context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) | `PATCH` | REST | Update source metadata | You need to merge `metadata` or `additional_metadata` onto one existing source without re-ingesting. | -| [`/context`](/api-reference/v2/endpoint/delete-source) | `DELETE` | `context.delete` | Delete sources or memories | You need to remove one or more knowledge sources or memories by ID. | -| [`/context/relations`](/api-reference/v2/endpoint/source-relations) | `GET` | `context.relations` | Inspect entity relationships | You need graph relations for a source or collection. | -| [`/query`](/api-reference/v2/endpoint/query) | `POST` | `query` | Unified query over knowledge, memories, or both | You need retrieval with `hybrid` or `text` query across `type: "knowledge"`, `type: "memory"`, or `type: "all"`. | +## Every endpoint + +The Python SDK method is listed for each endpoint; the TypeScript SDK uses the same names in camelCase, such as `deleteCollection`. Every endpoint page shows Python, TypeScript, and cURL. + +### Databases + +Create and manage the databases that store your context. [Overview](/api-reference/v2/endpoint/tenants-overview). + +| Endpoint | Method | SDK method | Purpose | +|---|---|---|---| +| [`/databases`](/api-reference/v2/endpoint/create-tenant) | `POST` | `databases.create` | Create a database | +| [`/databases/{database}/metadata-schema`](/api-reference/v2/endpoint/get-metadata-schema) | `GET` | `databases.get_metadata_schema` | Read metadata schema fields | +| [`/databases/{database}/metadata-schema`](/api-reference/v2/endpoint/update-metadata-schema) | `PATCH` | `databases.update_metadata_schema` | Add metadata schema fields | +| [`/databases`](/api-reference/v2/endpoint/list-tenants) | `GET` | `databases.list` | List databases | +| [`/databases`](/api-reference/v2/endpoint/delete-tenant) | `DELETE` | `databases.delete` | Delete a database | +| [`/databases/status`](/api-reference/v2/endpoint/tenant-status) | `GET` | `databases.status` | Check provisioning readiness | +| [`/databases/collections`](/api-reference/v2/endpoint/list-sub-tenants) | `GET` | `databases.collections` | List active collections | +| [`/databases/collections`](/api-reference/v2/endpoint/delete-collection) | `DELETE` | `databases.delete_collection` | Delete a collection | +| [`/databases/stats`](/api-reference/v2/endpoint/tenant-stats) | `GET` | `databases.stats` | Get usage statistics | + +### Context + +Ingest, check, list, inspect, edit, and delete what you store. [Overview](/api-reference/v2/endpoint/sources-overview). + +| Endpoint | Method | SDK method | Purpose | +|---|---|---|---| +| [`/context/ingest`](/api-reference/v2/endpoint/ingest-context) | `POST` | `context.ingest` | Ingest knowledge or memories | +| [`/context/status`](/api-reference/v2/endpoint/source-status) | `GET` | `context.status` | Check processing status | +| [`/context/inspect`](/api-reference/v2/endpoint/fetch-content) | `GET` | `context.inspect` | Inspect original source content or presigned URL | +| [`/context/list`](/api-reference/v2/endpoint/list-documents) | `POST` | `context.list` | Browse knowledge or memories | +| [`/context/{id}/metadata`](/api-reference/v2/endpoint/update-source-metadata) | `PATCH` | `context.update_source_metadata` | Update source metadata | +| [`/context`](/api-reference/v2/endpoint/delete-source) | `DELETE` | `context.delete` | Delete sources or memories | +| [`/context/relations`](/api-reference/v2/endpoint/source-relations) | `GET` | `context.relations` | Inspect entity relationships | +| [`/context/{id}/subgraph`](/api-reference/v2/endpoint/subgraph) | `GET` | `context.subgraph` | Get the connected subgraph | + +### Query + +Retrieve context, and tell HydraDB how a query performed. [Overview](/api-reference/v2/endpoint/query-overview). + +| Endpoint | Method | SDK method | Purpose | +|---|---|---|---| +| [`/query`](/api-reference/v2/endpoint/query) | `POST` | `query` | Unified query over knowledge, memories, or both | +| [`/feedback`](/api-reference/v2/endpoint/submit-feedback) | `POST` | `feedback.submit` | Submit query feedback | + +### Connectors + +Sync data from 70 apps without writing ingestion code. [Overview](/api-reference/v2/endpoint/connectors-overview). + +| Endpoint | Method | SDK method | Purpose | +|---|---|---|---| +| [`/connectors/providers`](/api-reference/v2/endpoint/list-connector-providers) | `GET` | `list_providers` | List connector providers | +| [`/connectors`](/api-reference/v2/endpoint/create-connector) | `POST` | `connectors.create` | Create a connector | +| [`/connectors/{id}/discover`](/api-reference/v2/endpoint/discover-connector-resources) | `GET` | `connectors.discover` | Discover resources | +| [`/connectors/{id}/configure`](/api-reference/v2/endpoint/configure-connector) | `POST` | `connectors.configure` | Configure a connector | +| [`/connectors/{id}/status`](/api-reference/v2/endpoint/get-connector-status) | `GET` | `connectors.status` | Get connector status | +| [`/connectors`](/api-reference/v2/endpoint/list-connectors) | `GET` | `connectors.list` | List connectors | +| [`/connectors/{id}`](/api-reference/v2/endpoint/get-connector) | `GET` | `connectors.get` | Get a connector | +| [`/connectors/{id}/resources`](/api-reference/v2/endpoint/connector-resources) | `GET` | `connectors.list_resources` | List connector resources | +| [`/connectors/{id}/sync`](/api-reference/v2/endpoint/sync-connector) | `POST` | `connectors.sync` | Sync a connector | +| [`/connectors/{id}/pause`](/api-reference/v2/endpoint/pause-connector) | `POST` | `connectors.pause` | Pause a connector | +| [`/connectors/{id}/resume`](/api-reference/v2/endpoint/resume-connector) | `POST` | `connectors.resume` | Resume a connector | +| [`/connectors/{id}`](/api-reference/v2/endpoint/update-connector) | `PATCH` | `connectors.update` | Update a connector | +| [`/connectors/{id}/resources/{resource_id}`](/api-reference/v2/endpoint/update-connector-resource) | `PATCH` | `connectors.update_resource_acl` | Update a connector resource | +| [`/connectors/{id}/resources`](/api-reference/v2/endpoint/add-connector-resource) | `POST` | `connectors.create_resource` | Add a connector resource | +| [`/connectors/{id}/resources/{resource_id}`](/api-reference/v2/endpoint/delete-connector-resource) | `DELETE` | `connectors.delete_resource` | Delete a connector resource | +| [`/connectors/{id}`](/api-reference/v2/endpoint/delete-connector) | `DELETE` | `connectors.delete` | Delete a connector | + +### Webhooks + +Get notified when indexing finishes instead of polling. [Overview](/api-reference/v2/endpoint/webhooks-overview). + +| Endpoint | Method | SDK method | Purpose | +|---|---|---|---| +| [`/webhooks/indexing`](/api-reference/v2/endpoint/register-webhook) | `POST` | `webhooks.register` | Register a webhook | +| [`/webhooks/indexing`](/api-reference/v2/endpoint/get-webhook) | `GET` | `webhooks.get` | Get the webhook | +| [`/webhooks/indexing`](/api-reference/v2/endpoint/delete-webhook) | `DELETE` | `webhooks.delete` | Delete the webhook | +| [`/webhooks/indexing/test`](/api-reference/v2/endpoint/test-webhook) | `POST` | `webhooks.test` | Send a test delivery | +| [`/webhooks/indexing/deliveries`](/api-reference/v2/endpoint/list-webhook-deliveries) | `GET` | `webhooks.list_deliveries` | List deliveries | +| [`/webhooks/indexing/deliveries/{delivery_id}`](/api-reference/v2/endpoint/get-webhook-delivery) | `GET` | `webhooks.get_delivery` | Get a delivery | +| [`/webhooks/indexing/deliveries/{delivery_id}/retry`](/api-reference/v2/endpoint/retry-webhook-delivery) | `POST` | `webhooks.retry_delivery` | Retry a delivery | +| [`/webhooks/indexing/signing-secret`](/api-reference/v2/endpoint/set-webhook-signing-secret) | `POST` | `webhooks.set_signing_secret` | Generate or replace the signing secret | +| [`/webhooks/indexing/signing-secret`](/api-reference/v2/endpoint/disable-webhook-signing) | `DELETE` | `webhooks.clear_signing_secret` | Disable signing | ## Conventions -**Authentication:** Every endpoint requires `Authorization: Bearer ` in the request header. Get your key at [app.hydradb.com](https://app.hydradb.com). +**Authentication:** Every endpoint requires `Authorization: Bearer `. Get your key at [dashboard.hydradb.com](https://dashboard.hydradb.com). The [SDKs](/api-reference/v2/sdks) set it, and the version header, for you. **Versioning:** Send `API-Version: 2` with every request. @@ -128,7 +138,7 @@ curl -X POST 'https://api.hydradb.com/query' \ }' ``` -**Response envelope:** Core v2 endpoints (`/databases`, `/context/*`, and `/query`) return a consistent envelope. Endpoint pages show the full envelope; the resource-specific payload lives under `data`. Webhook management endpoints (`/webhooks/indexing*`) are the exception: they return their documented response object directly. +**Response envelope:** Core v2 endpoints (`/databases`, `/context/*`, `/query`, `/feedback`, and `/webhooks/indexing*`) return a consistent envelope. Endpoint pages show the full envelope; the resource-specific payload lives under `data`. Connector endpoints (`/connectors*`) and `PATCH /databases/{database}/metadata-schema` are the exception: they return their success body without the envelope. ```json { @@ -142,49 +152,29 @@ curl -X POST 'https://api.hydradb.com/query' \ } ``` -Errors use the same envelope with `success: false`, `data: null`, and an `error` object containing `code` and `message`. - -`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/multi-tenant#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/multi-tenant#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. +Errors use the same envelope on every endpoint, with `success: false` and an `error` object containing `code` and `message`. Most errors have `data: null`. Some [Delete Context](/api-reference/v2/endpoint/delete-source) errors include per-ID results under `data`. -- **Pagination.** Listing endpoints (`/context/list`) return pagination fields for browsing large result sets. +Legacy requests can include deprecation notices. See [Migrating from tenant fields](/essentials/v2/multi-tenant#7-migrating-from-the-legacy-tenant-and-sub-tenant-fields). -- **Parameter casing.** The REST API uses snake_case (`database`). The TypeScript SDK accepts the same snake_case keys; method names are camelCase when generated for TypeScript. The Python SDK uses snake_case throughout. +- **Database scoping:** Use `database` to select a database and `collection` to scope context within it. Omitting `collection` uses the default collection. -- **Query modes.** `POST /query` supports `query_by: "hybrid"` or `"text"` and `type: "knowledge"`, `"memory"`, or `"all"`. The same `type` enum is used across ingestion, listing, deletion, and query; query additionally accepts `"all"`. +- **Async operations:** Database creation, database and collection deletion, and content ingestion are asynchronous. They return immediately after queuing. Use the relevant status endpoint to confirm completion before downstream operations. -**Status codes:** Successful responses return `200` (or `202` for async accepts). Errors follow standard HTTP semantics: +- **Pagination:** Listing endpoints (`/context/list`) return pagination fields for browsing large result sets. -| Code | Meaning | -|---|---| -| `200` | Success | -| `202` | Accepted (async operation queued) | -| `400` | Invalid parameters | -| `401` | Authentication required | -| `403` | Forbidden | -| `404` | Resource not found | -| `409` | Conflict (e.g., database already exists) | -| `422` | Validation error | -| `429` | Rate limit exceeded | -| `500` | Internal server error | -| `503` | Service unavailable | +- **Parameter casing:** The REST API uses snake_case (`database`, `max_results`). The TypeScript SDK uses camelCase keys and method names (`maxResults`, `deleteCollection`) and ignores request keys it does not recognize, including snake_case ones. The Python SDK uses snake_case throughout. See [SDKs](/api-reference/v2/sdks#naming-conventions). -See [Error Responses](/api-reference/v2/error-responses) for response shapes, error codes, and retry patterns. +**Status codes:** `200` for success and `202` when an async operation is queued. See [Error Responses](/api-reference/v2/error-responses) for response shapes, error codes, and retry patterns. ## Rate limits -Rate limits apply per API key. For production deployments, build retry logic with exponential backoff against the `429` response. Contact [founders@hydradb.com](mailto:founders@hydradb.com) for current limit values. +Rate limits apply per API key. Both SDKs retry `429` responses automatically. For raw HTTP, use exponential backoff; see [Error Responses](/api-reference/v2/error-responses#retry-pattern). Contact [founders@hydradb.com](mailto:founders@hydradb.com) for current limit values. ## Next steps Existing v1 endpoints remain available under the v1 API Reference. - **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, memories, Query, and metadata -- **Go deeper:** [Usage](/essentials/v2/query) covers each primitive in depth +- **Understand the model:** [Core Concepts](/get-started/v2/core-concepts) explains databases, context, and queries +- **Query options:** [Query guide](/essentials/v2/query) explains retrieval modes and ranking - **Install an SDK:** [Python](https://pypi.org/project/hydradb-sdk/) · [TypeScript](https://www.npmjs.com/package/@hydradb/sdk) diff --git a/api-reference/v2/openapi.json b/api-reference/v2/openapi.json index 63efe57c..f6b95168 100644 --- a/api-reference/v2/openapi.json +++ b/api-reference/v2/openapi.json @@ -21,13 +21,21 @@ }, "additional_metadata": { "additionalProperties": {}, - "description": "Key-value pairs merged into the custom attributes (`additional_metadata`) of every object synced from this resource. Provider-generated fields win on conflict.", + "description": "Key-value pairs merged into the additional metadata (`additional_metadata`) of every object synced from this resource. Provider-generated fields win on conflict.", "example": { "author": "ada", "doc_version": 3 }, "type": "object" }, + "backfill_error": { + "description": "Most recent failure fetching historical records. Cleared when progress resumes.", + "type": "string" + }, + "backfill_error_at": { + "description": "When the historical fetch last failed, in RFC3339 format.", + "type": "string" + }, "backfill_oldest": { "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", @@ -65,7 +73,7 @@ }, "metadata": { "additionalProperties": {}, - "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.", + "description": "Key-value pairs merged into the metadata (`metadata`) of every object synced from this resource. The system fields `connector_id` and `provider` always win on conflict.", "example": { "department": "finance", "priority": 7 @@ -88,10 +96,11 @@ "provider_metadata": { "additionalProperties": {}, "description": "Additional provider-supplied metadata for this resource.", - "example": { - "workspace_id": "T12345ACME" - }, - "type": "object" + "example": null, + "type": [ + "object", + "null" + ] }, "resource_id": { "description": "Resource identifier from the Discover endpoint.", @@ -154,12 +163,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" }, @@ -174,12 +183,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" @@ -220,84 +229,6 @@ ] }, "feedback.SubmitRequest": { - "anyOf": [ - { - "properties": { - "feedback": { - "minLength": 1, - "pattern": "\\S" - } - }, - "required": [ - "feedback" - ] - }, - { - "properties": { - "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.", @@ -317,8 +248,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": "The known right answer: an expected `answer`, the `source_ids` that contain it, or both. When sent, `feedback` becomes optional.", "example": { "source_ids": [ "HydraDoc1234", @@ -347,7 +282,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" @@ -358,7 +293,7 @@ }, "sub_tenant_id": { "deprecated": true, - "description": "deprecated: use collection", + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "minLength": 1, "type": "string", @@ -367,7 +302,7 @@ }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "minLength": 1, "type": "string", @@ -412,33 +347,48 @@ "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" + "type": [ + "string", + "null" + ] }, "content_base64": { - "description": "Base64-encoded binary content, for binary file types.", - "type": "string" + "description": "The stored content, base64-encoded, when it is not UTF-8 text. `null` in `url` mode.", + "type": [ + "string", + "null" + ] }, "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" + "type": [ + "string", + "null" + ] }, "error": { - "description": "Error message, empty string on success.", - "example": "", - "type": "string" + "description": "Error message, or `null` on success.", + "example": null, + "type": [ + "string", + "null" + ] }, "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" + "type": [ + "string", + "null" + ] }, "message": { "description": "Human-readable result message.", @@ -446,18 +396,24 @@ "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" + "type": [ + "string", + "null" + ] }, "size_bytes": { - "description": "File size in bytes.", + "description": "Size of the stored content in bytes.", "example": 20480, - "type": "integer" + "type": [ + "integer", + "null" + ] }, "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" @@ -467,13 +423,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" }, @@ -486,27 +437,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" @@ -518,52 +469,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" }, @@ -572,31 +523,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 absent after an `acl` update, newly added principals may not find the context until it is re-indexed.", "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" } @@ -611,13 +562,16 @@ "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": "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": { - "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" + "type": [ + "string", + "null" + ] }, "name": { "description": "Human-readable label for this resource.", @@ -630,7 +584,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" }, @@ -645,7 +599,7 @@ "graph.GraphRelationsResponse": { "properties": { "auxiliary_relations": { - "description": "AuxiliaryRelations carries the structural graph around the entity\nrelations: Entity->Source presence, Source->Comment/Attachment,\nActor->Source/Comment, and Source->Source 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 relations: entity appearances, comments, attachments, authors and context links. Same shape as `relations`; not paged by `limit`.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -690,12 +644,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" }, @@ -705,12 +659,15 @@ "type": "string" }, "next_cursor": { - "description": "NO omitempty", - "example": 0.5, - "type": "number" + "description": "Opaque cursor for the next page; pass it back as `cursor` exactly as returned. `null` when there are no more relations.", + "example": null, + "type": [ + "number", + "null" + ] }, "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", @@ -756,7 +713,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" @@ -772,9 +729,12 @@ "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" + "type": [ + "string", + "null" + ] }, "confidence": { "description": "Confidence score, from 0 to 1.", @@ -797,27 +757,36 @@ "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" + "type": [ + "string", + "null" + ] }, "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->Chunk-HAS_CHUNK->Source, 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 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" }, "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" + "type": [ + "string", + "null" + ] }, "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" + "type": [ + "string", + "null" + ] }, "timestamp": { - "description": "RFC3339 timestamp associated with this item.", + "description": "When the relation was recorded (RFC 3339).", "example": "2026-07-02T10:00:00Z", "type": "string" } @@ -827,7 +796,7 @@ "graph.SourceSubgraphResponse": { "properties": { "auxiliary_relations": { - "description": "AuxiliaryRelations carries the structural graph around the member\nsources: Entity->Source presence, Source->Comment/Attachment and\nActor->Source/Comment links. Same item shape as Relations, matching\nGraphRelationsResponse so the dashboard renderer works unchanged.", + "description": "The structural graph around the members: entities, comments, attachments and authors. Same shape as `relations`.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -872,17 +841,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" }, @@ -892,7 +861,7 @@ "type": "string" }, "relations": { - "description": "Relations holds the Source->Source 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, plus `same_thread` and `child_of` relations (marked `synthesized`) for shared threads and hierarchies.", "example": [ { "chunk_id": "HydraEmbeddings123_0", @@ -937,10 +906,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", @@ -978,31 +948,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` (linked to but not ingested yet) or `placeholder` (known only by its provider id).", "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": { @@ -1066,41 +1039,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.", - "example": true, - "type": "boolean" - } - }, - "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.", + "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" } @@ -1119,12 +1058,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -1153,7 +1098,7 @@ "example": { "content": "# Q4 Report\n\nRevenue grew 23% quarter over quarter.", "content_type": "application/pdf", - "error": "", + "error": null, "id": "HydraDoc1234", "inferred_content": "Summary: Q4 revenue rose 23% QoQ, driven by enterprise expansion.", "message": "Success", @@ -1163,12 +1108,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -1195,7 +1146,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, @@ -1219,12 +1169,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -1291,7 +1247,7 @@ "auxiliary_truncated": true, "is_truncated": false, "message": "Success", - "next_cursor": 0.5, + "next_cursor": null, "relations": [ { "chunk_id": "HydraEmbeddings123_0", @@ -1333,12 +1289,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -1457,49 +1419,18 @@ } }, "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-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" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -1539,12 +1470,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -1575,7 +1512,8 @@ "message": "Success", "results": [ { - "error": "", + "error": null, + "error_code": null, "filename": "policy.pdf", "id": "HydraDoc1234", "infer": true, @@ -1589,12 +1527,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -1668,7 +1612,6 @@ "app_kind": "slack", "app_provider": "slack", "collection": "team_docs", - "comments_truncated": true, "database": "acme_corp", "description": "Internal overview of the Project Phoenix rollout.", "memory_id": "memory_1234", @@ -1687,76 +1630,18 @@ } }, "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_ChunkInspectResult": { - "properties": { - "data": { - "$ref": "#/components/schemas/search.ChunkInspectResult", - "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" - } - ], - "is_truncated": false, - "message": "Success", - "success": true - } - }, - "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -1781,9 +1666,8 @@ "handler.Envelope-search_V2RetrievalResult": { "properties": { "data": { - "$ref": "#/components/schemas/search.V2RetrievalResult", + "description": "Query results: the structured object by default, or a single string rendering of the same results when the request set response_format to markdown.", "example": { - "additional_context": "The user is a senior engineer onboarding to the platform.", "chunks": [ { "additional_metadata": { @@ -1811,6 +1695,19 @@ "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" + }, "graph_context": { "chunk_id_to_group_ids": { "HydraEmbeddings123_0": [ @@ -1819,7 +1716,7 @@ }, "chunk_relations": [ { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "combined_context": "Acme Corp deploys HydraDB: Acme Corp deploys HydraDB in production for context retrieval.", "group_id": "grp_1234", "relevancy_score": 0.87, "source_chunk_ids": [ @@ -1829,8 +1726,11 @@ "triplets": [ { "relation": { - "confidence": 0.92, - "predicate": "works_at" + "canonical_predicate": "works_at", + "context": "Ada works at Acme Corp.", + "raw_predicate": "works at", + "source_entity_id": "entity_1a2b", + "target_entity_id": "entity_3c4d" }, "source": { "entity_id": "entity_1a2b", @@ -1848,7 +1748,7 @@ ], "query_paths": [ { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "combined_context": "Acme Corp deploys HydraDB: Acme Corp deploys HydraDB in production for context retrieval.", "group_id": "grp_1234", "relevancy_score": 0.87, "source_chunk_ids": [ @@ -1858,8 +1758,11 @@ "triplets": [ { "relation": { - "confidence": 0.92, - "predicate": "works_at" + "canonical_predicate": "works_at", + "context": "Ada works at Acme Corp.", + "raw_predicate": "works at", + "source_entity_id": "entity_1a2b", + "target_entity_id": "entity_3c4d" }, "source": { "entity_id": "entity_1a2b", @@ -1876,48 +1779,6 @@ } ] }, - "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, @@ -1948,25 +1809,33 @@ "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", + "title": "Result object" + }, + { + "description": "String rendering of the results, returned when the request set response_format to markdown.", + "title": "Markdown string", + "type": "string" } - } + ] }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2007,12 +1876,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2055,13 +1930,66 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null + }, + "meta": { + "$ref": "#/components/schemas/handler.responseMeta", "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" + "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" } }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null + }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", "example": { @@ -2099,12 +2027,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2138,12 +2072,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2177,12 +2117,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2235,12 +2181,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2282,12 +2234,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2326,12 +2284,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2362,8 +2326,8 @@ "created_at": "2026-07-02T10:00:00Z", "delivery_id": "dlv_9f8e7d6c", "doc_id": "HydraDoc1234", - "error_code": "", - "error_message": "", + "error_code": null, + "error_message": null, "event_type": "indexing.status_changed", "indexing_status": "completed", "status": "completed", @@ -2371,12 +2335,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2410,24 +2380,30 @@ "created_at": "2026-07-02T10:00:00Z", "delivery_id": "dlv_9f8e7d6c", "doc_id": "HydraDoc1234", - "error_code": "", - "error_message": "", + "error_code": null, + "error_message": null, "event_type": "indexing.status_changed", "indexing_status": "completed", "status": "completed", "updated_at": "2026-07-02T10:00:05Z" } ], - "next_cursor": "eyJvZmZzZXQiOjUwfQ==" + "next_cursor": null } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2460,12 +2436,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2498,12 +2480,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2535,12 +2523,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2576,12 +2570,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2619,12 +2619,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2657,12 +2663,18 @@ } }, "error": { - "$ref": "#/components/schemas/handler.apiError", - "description": "Error message, empty string on success.", - "example": { - "code": "DATABASE_NOT_FOUND", - "message": "Database not found" - } + "anyOf": [ + { + "$ref": "#/components/schemas/handler.apiError", + "title": "Error details" + }, + { + "title": "No error", + "type": "null" + } + ], + "description": "Error details on failure, or null on success.", + "example": null }, "meta": { "$ref": "#/components/schemas/handler.responseMeta", @@ -2766,7 +2778,7 @@ }, "success": { "description": "Whether the request succeeded.", - "example": true, + "example": false, "type": "boolean" } }, @@ -2837,10 +2849,21 @@ "example": "DATABASE_NOT_FOUND", "type": "string" }, + "details": { + "additionalProperties": { + "type": "string" + }, + "description": "Extra facts about the error, as string pairs. Omitted when there are none.", + "type": "object" + }, "message": { "description": "Human-readable description of the error.", "example": "Database not found", "type": "string" + }, + "param": { + "description": "The request field that caused the error. Omitted when no single field is at fault.", + "type": "string" } }, "type": "object" @@ -2873,8 +2896,10 @@ }, "rank": { "description": "Catalog display order; lower ranks appear first. `null` when unranked.", - "example": 1, - "type": "integer" + "type": [ + "integer", + "null" + ] }, "rbac_description": { "description": "One-line summary of which provider permissions are captured as document access rules. Not returned by this endpoint.", @@ -2981,6 +3006,14 @@ "meta": { "$ref": "#/components/schemas/handler.configureResponseMeta" }, + "unverified": { + "description": "Resources that were saved without being checked, because the provider was rate limiting, slow, or unreachable. Their first sync shows whether they can be read.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, "warnings": { "description": "Resources that were saved but returned no records when probed. They stay configured and will index nothing until they have data.", "items": { @@ -3157,30 +3190,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": { @@ -3195,7 +3204,7 @@ }, "credentials": { "additionalProperties": {}, - "description": "Provider-specific credentials. Their shape is the provider's `credential_schema` from `GET /connectors/providers?id=`, for example `{\"access_token\": \"...\"}`.", + "description": "Provider-specific credentials. Their shape is the provider's `credential_schema` from `GET /connectors/providers?id=\u003cprovider\u003e`, for example `{\"access_token\": \"...\"}`.", "example": { "api_token": "xoxb-..." }, @@ -3318,6 +3327,9 @@ "example": "Success", "type": "string" }, + "meta": { + "$ref": "#/components/schemas/handler.configureResponseMeta" + }, "name": { "description": "Human-readable label for this connector.", "example": "general", @@ -3426,6 +3438,31 @@ }, "type": "object" }, + "handler.connectorLimitView": { + "description": "The plan's limit on the number of connectors.", + "properties": { + "count": { + "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 explanation, safe to show to users as is.", + "example": "Success", + "type": "string" + }, + "plan": { + "description": "Plan the organization is on.", + "type": "string" + } + }, + "type": "object" + }, "handler.connectorListResponse": { "properties": { "connector_limit": { @@ -3496,6 +3533,25 @@ }, "type": "object" }, + "handler.connectorPauseResponse": { + "properties": { + "connector_id": { + "description": "Connector this resource belongs to.", + "example": "conn_abc123", + "type": "string" + }, + "paused": { + "description": "`true` after a pause, `false` after a resume.", + "example": true, + "type": "boolean" + }, + "paused_at": { + "description": "When the connector was paused (RFC 3339). Absent after a resume.", + "type": "string" + } + }, + "type": "object" + }, "handler.connectorResourceStatus": { "properties": { "acl_warning": { @@ -3510,6 +3566,14 @@ "description": "What to do to fix this resource, when there is something you can do.", "type": "string" }, + "backfill_error": { + "description": "Most recent failure fetching historical records, when present.", + "type": "string" + }, + "backfill_error_at": { + "description": "When the historical fetch last failed, in RFC3339 format.", + "type": "string" + }, "checked_at": { "description": "When this resource's health was last recorded (RFC 3339).", "type": "string" @@ -3553,7 +3617,7 @@ "type": "boolean" }, "status": { - "description": "Result of this resource's last sync: `ok`, `empty` (ran and found nothing new), `failed`, `checking`, or `unknown` (no sync has reported yet).", + "description": "Result of this resource's last sync: `ok`, `empty` (ran and found nothing new), `partial` (incomplete; deletes deferred), `failed`, `checking`, or `unknown` (no sync has reported yet).", "example": "ok", "type": "string" }, @@ -3594,9 +3658,7 @@ "priority": 7 }, "provider_cursor": "1699999999.000100", - "provider_metadata": { - "workspace_id": "T12345ACME" - }, + "provider_metadata": null, "resource_id": "C0123456789", "resource_type": "channel", "status": "completed", @@ -3708,7 +3770,7 @@ "uniqueItems": false }, "status": { - "description": "Overall health: `healthy`, `degraded` (partly failing, still retrying), `failed` (you must act, e.g. reconnect), `checking` (testing new resources), `paused`, or `capped` (a plan limit paused syncs).", + "description": "Overall health: `healthy`, `degraded` (partly failing, still retrying), `failed` (you must act: reconnect or recreate a deleted database), `checking`, `paused`, or `capped`.", "example": "healthy", "type": "string" }, @@ -3737,7 +3799,7 @@ "properties": { "credentials": { "additionalProperties": {}, - "description": "Provider-specific credentials. Their shape is the provider's `credential_schema` from `GET /connectors/providers?id=`, for example `{\"access_token\": \"...\"}`.", + "description": "Provider-specific credentials. Their shape is the provider's `credential_schema` from `GET /connectors/providers?id=\u003cprovider\u003e`, for example `{\"access_token\": \"...\"}`.", "example": { "api_token": "xoxb-..." }, @@ -3798,7 +3860,7 @@ "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. Send the full list, `[]` or `null` for private, or `[\"__public__\"]` for every identified caller. Omit to leave it unchanged.", "items": { "type": "string" }, @@ -3807,7 +3869,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 merged into the context's `additional_metadata`. At most 1 KiB as compact JSON.", "example": { "author": "ada", "doc_version": 3 @@ -3815,18 +3877,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 merged into the context's `metadata`. Keys must be declared in the schema when there is one. At most 16 KiB as compact JSON.", "example": { "department": "legal", "priority": 7 @@ -3836,20 +3898,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" @@ -3857,7 +3919,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 @@ -3868,22 +3930,7 @@ }, "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": { + "handler.deprecationNotice": { "properties": { "deprecated": { "description": "Whether this response concerns a deprecated field or route.", @@ -3913,33 +3960,6 @@ }, "type": "object" }, - "handler.discoverPreviewReq": { - "properties": { - "auth_type": { - "description": "Authentication method for the provider connection (e.g. `api_token`, `oauth`).", - "example": "api_token", - "type": "string" - }, - "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": { @@ -4029,6 +4049,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "acme_corp", "type": "string", "x-deprecated": "true" @@ -4036,6 +4057,25 @@ }, "type": "object" }, + "handler.planCapView": { + "description": "The plan limit that is currently stopping syncs.", + "properties": { + "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" + } + }, + "type": "object" + }, "handler.providerListResponse": { "properties": { "providers": { @@ -4046,7 +4086,6 @@ "is_beta": true, "moveit_support": true, "provider": "slack", - "rank": 1, "rbac_support": true, "supported": true, "webhook_support": true @@ -4073,7 +4112,7 @@ }, "additional_metadata": { "additionalProperties": {}, - "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.", + "description": "Key-value pairs merged into the additional metadata of every synced object. Up to 1 KiB of compact JSON, checked when synced objects are ingested.", "example": { "author": "ada", "doc_version": 3 @@ -4107,7 +4146,7 @@ }, "metadata": { "additionalProperties": {}, - "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.", + "description": "Key-value pairs merged into the metadata of every synced object. Up to 16 KiB of compact JSON, checked when synced objects are ingested.", "example": { "department": "finance", "priority": 7 @@ -4150,6 +4189,166 @@ ], "type": "object" }, + "handler.resourceCreateResponse": { + "properties": { + "acl": { + "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" + }, + "type": "array", + "uniqueItems": false + }, + "acl_warning": { + "description": "Why this resource's permissions could not be read. Without an access rule of yours, its objects are readable by everyone meanwhile; with one, your rule applies. Clears on the next successful read.", + "type": "string" + }, + "acl_warning_at": { + "description": "When `acl_warning` last changed (RFC 3339). An unchanged warning keeps its original time.", + "type": "string" + }, + "additional_metadata": { + "additionalProperties": {}, + "description": "Key-value pairs merged into the additional metadata (`additional_metadata`) of every object synced from this resource. Provider-generated fields win on conflict.", + "example": { + "author": "ada", + "doc_version": 3 + }, + "type": "object" + }, + "backfill_error": { + "description": "Most recent failure fetching historical records. Cleared when progress resumes.", + "type": "string" + }, + "backfill_error_at": { + "description": "When the historical fetch last failed, in RFC3339 format.", + "type": "string" + }, + "backfill_oldest": { + "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. Formerly `sub_tenant_id_override`.", + "type": "string" + }, + "connector_id": { + "description": "Connector this resource belongs to.", + "example": "conn_abc123", + "type": "string" + }, + "custom_instructions": { + "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": { + "description": "Database this resource's synced objects are routed to. Empty means the connector's own database.", + "type": "string" + }, + "display_name": { + "description": "Human-readable name for this resource.", + "example": "general", + "type": "string" + }, + "filters": { + "additionalProperties": {}, + "description": "Provider-specific filters applied during sync (e.g. `{\"lookback_days\": 30}`).", + "example": { + "channel": "general" + }, + "type": "object" + }, + "metadata": { + "additionalProperties": {}, + "description": "Key-value pairs merged into the metadata (`metadata`) of every object synced from this resource. The system fields `connector_id` and `provider` always win on conflict.", + "example": { + "department": "finance", + "priority": 7 + }, + "type": "object" + }, + "page_acl_warning": { + "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": { + "description": "When `page_acl_warning` last changed (RFC 3339).", + "type": "string" + }, + "provider_cursor": { + "description": "Bookmark of the last synced position. Non-empty value confirms the first sync has run.", + "example": "1699999999.000100", + "type": "string" + }, + "provider_metadata": { + "additionalProperties": {}, + "description": "Additional provider-supplied metadata for this resource.", + "example": null, + "type": [ + "object", + "null" + ] + }, + "resource_id": { + "description": "Resource identifier from the Discover endpoint.", + "example": "C0123456789", + "type": "string" + }, + "resource_type": { + "description": "Type of resource within the provider (e.g. `channel`, `repo`, `linear_team`).", + "example": "channel", + "type": "string" + }, + "status": { + "description": "Current sync state of this resource (e.g. `active`, `paused`).", + "example": "active", + "type": "string" + }, + "sub_tenant_id_override": { + "deprecated": true, + "description": "Deprecated: use `collection_override`.", + "type": "string", + "x-deprecated": "true" + }, + "sync_blocked": { + "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" + }, + "sync_blocked_at": { + "description": "When syncing of this resource was stopped (RFC 3339). Present only while `sync_blocked` is `true`.", + "type": "string" + }, + "sync_blocked_reason": { + "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": "Deprecated: use `database_override`.", + "type": "string", + "x-deprecated": "true" + }, + "unverified": { + "description": "Present when the resource was saved without being checked, because the provider was rate limiting, slow, or unreachable. Its first sync shows whether it can be read.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "warnings": { + "description": "Present when the resource was saved but returned no records when checked. It stays configured and will index nothing until it has data.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object" + }, "handler.resourceDeleteResponse": { "properties": { "connector_id": { @@ -4182,7 +4381,7 @@ }, "additional_metadata": { "additionalProperties": {}, - "description": "Free-form key-value pairs merged into the custom attributes of every synced object. Provider-generated fields win on conflict.", + "description": "Free-form key-value pairs merged into the additional metadata of every synced object. Provider-generated fields win on conflict.", "example": { "author": "ada", "doc_version": 3 @@ -4205,7 +4404,7 @@ }, "metadata": { "additionalProperties": {}, - "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.", + "description": "Key-value pairs merged into the metadata 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 @@ -4255,10 +4454,59 @@ ], "type": "object" }, + "handler.resourceUpdateReq": { + "properties": { + "acl": { + "description": "Who can read this resource's objects: emails, `group:\u003cprovider\u003e:\u003cid\u003e`, `domain:\u003cdomain\u003e`, or `[\"__public__\"]`. `[]` allows nobody. Provider permissions, where read, take over at the next sync.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "custom_instructions": { + "description": "Ingestion and indexing instructions for this resource, replacing the connector's. Omit to leave unchanged; `\"\"` clears back to the connector's. Up to 4000 characters, applied from the next sync.", + "type": "string" + } + }, + "type": "object" + }, + "handler.resourceUpdateResponse": { + "properties": { + "acl": { + "description": "The access rule as stored. Present only when the request set `acl`.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, + "connector_id": { + "description": "Connector this resource belongs to.", + "example": "conn_abc123", + "type": "string" + }, + "custom_instructions": { + "description": "The resource's instructions as stored; `\"\"` means they were cleared. Present only when the request set them.", + "type": "string" + }, + "custom_instructions_updated": { + "description": "`true` when this request set or cleared the resource's `custom_instructions`.", + "example": true, + "type": "boolean" + }, + "resource_id": { + "description": "Resource identifier from the Discover endpoint.", + "example": "C0123456789", + "type": "string" + } + }, + "type": "object" + }, "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": { @@ -4272,7 +4520,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" }, @@ -4282,7 +4530,10 @@ "latency_ms": { "description": "Server-side processing time in milliseconds.", "example": 12.3, - "type": "number" + "type": [ + "number", + "null" + ] }, "request_id": { "description": "Unique identifier for this request, useful for support and tracing.", @@ -4296,12 +4547,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" @@ -4309,25 +4562,7 @@ }, "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": { + "handler.tableConfigEntry": { "properties": { "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`.", @@ -4345,7 +4580,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", @@ -4363,7 +4598,7 @@ "ingestion.V2BatchProcessingStatus": { "properties": { "statuses": { - "description": "Per-source indexing status results.", + "description": "One status per requested ID.", "example": [ { "error_code": "", @@ -4386,7 +4621,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" }, @@ -4396,10 +4631,11 @@ "type": "string" }, "results": { - "description": "Per-item results.", + "description": "One result per context, in request order.", "example": [ { - "error": "", + "error": null, + "error_code": null, "filename": "policy.pdf", "id": "HydraDoc1234", "infer": true, @@ -4416,13 +4652,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: true when every context was queued. Check `results[].status`; an accepted request can still include failures.", "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" } @@ -4432,44 +4668,55 @@ "ingestion.V2IngestResultItem": { "properties": { "error": { - "description": "Error is the failure message for this item, null on success. Both modes.", - "example": "", - "type": "string" + "description": "Why this context failed, or `null` on success.", + "example": null, + "type": [ + "string", + "null" + ] }, "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.", - "type": "string" + "description": "Machine-readable failure code, or `null` on success.", + "example": null, + "type": [ + "string", + "null" + ] }, "filename": { - "description": "Filename is the original filename as submitted. type=knowledge only.", + "description": "Name of the uploaded file, when present.", "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 declared relations created for this entry.", "example": 5, "type": "integer" }, "relations_error": { - "description": "RelationsError is the relation-extraction failure message, if any.\ntype=knowledge only.", + "description": "Why declared relations failed for this entry, when present.", "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" } @@ -4479,33 +4726,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": "`queued`, `processing`, `graph_creation`, `completed` or `errored`. Searchable from `graph_creation` on; `completed` and `errored` are final.", "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" @@ -4516,25 +4763,97 @@ "list.ContentFilter": { "properties": { "additional_metadata": { - "additionalProperties": {}, - "description": "Filters /context/list by document/additional metadata. Example: {\"author\": \"ada\"}.", + "additionalProperties": { + "not": { + "type": "null" + } + }, + "description": "Match additional metadata. `document_metadata` is also accepted. Use bare values.", "example": { "author": "ada" }, "type": "object" }, "metadata": { - "additionalProperties": {}, - "description": "Filters /context/list by tenant/source metadata. Example: {\"department\": \"finance\"}.", + "additionalProperties": { + "not": { + "type": "null" + } + }, + "description": "Match context metadata. `tenant_metadata` is also accepted. Use bare values, such as `{\"department\":\"finance\"}`.", "example": { "department": "finance" }, "type": "object" }, "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.", - "type": "object" + "allOf": [ + { + "$ref": "#/components/schemas/list.SourceFieldFilters" + } + ], + "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`.", + "example": { + "app_external_id": "C0123456789", + "app_kind": "slack", + "app_provider": "slack", + "description": "Internal overview of the Project Phoenix rollout.", + "timestamp": "2026-07-02T10:00:00Z", + "title": "Project Phoenix Overview", + "type": "knowledge", + "url": "https://docs.hydradb.com/phoenix" + } + } + }, + "type": "object" + }, + "list.SourceFieldFilters": { + "additionalProperties": false, + "description": "Filters on supported built-in context fields.", + "properties": { + "app_external_id": { + "description": "Exact value of this built-in context field.", + "example": "C0123456789", + "type": "string" + }, + "app_kind": { + "description": "Exact value of this built-in context field.", + "example": "slack", + "type": "string" + }, + "app_parent_id": { + "description": "Exact value of this built-in context field.", + "type": "string" + }, + "app_provider": { + "description": "Exact value of this built-in context field.", + "example": "slack", + "type": "string" + }, + "description": { + "description": "Exact value of this built-in context field.", + "example": "Internal overview of the Project Phoenix rollout.", + "type": "string" + }, + "timestamp": { + "description": "Exact value of this built-in context field.", + "example": "2026-07-02T10:00:00Z", + "type": "string" + }, + "title": { + "description": "Case-insensitive title prefix.", + "example": "Project Phoenix Overview", + "type": "string" + }, + "type": { + "description": "Exact value of this built-in context field.", + "example": "knowledge", + "type": "string" + }, + "url": { + "description": "Exact value of this built-in context field.", + "example": "https://docs.hydradb.com/phoenix", + "type": "string" } }, "type": "object" @@ -4542,7 +4861,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" }, @@ -4550,33 +4869,48 @@ "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" }, "metadata": { "department": "finance" + }, + "source_fields": { + "app_external_id": "C0123456789", + "app_kind": "slack", + "app_provider": "slack", + "description": "Internal overview of the Project Phoenix rollout.", + "timestamp": "2026-07-02T10:00:00Z", + "title": "Project Phoenix Overview", + "type": "knowledge", + "url": "https://docs.hydradb.com/phoenix" } } }, "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" @@ -4588,7 +4922,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`. Supported fields are `title`, `type`, `description`, `note`, `timestamp`, `metadata`, `additional_metadata` and `relations`.", "example": [ "id", "title", @@ -4601,34 +4935,35 @@ "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": { - "description": "Bucket to list: `knowledge` (default) or `memory`.", + "description": "Corpus to list, `knowledge`, `memory` or `all` for both. Defaults to `knowledge`.", "enum": [ "knowledge", - "memory" + "memory", + "all" ], "example": "knowledge", "type": "string" @@ -4655,7 +4990,7 @@ } }, "sources": { - "description": "Sources carries the rows when type=knowledge (the default).", + "description": "The listed context, one row per context.", "example": [ { "additional_metadata": { @@ -4690,18 +5025,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": "Memory rows returned when `type` is `memory`.", "example": [ { "additional_metadata": { @@ -4712,7 +5047,6 @@ "app_kind": "slack", "app_provider": "slack", "collection": "team_docs", - "comments_truncated": true, "database": "acme_corp", "description": "Internal overview of the Project Phoenix rollout.", "memory_id": "memory_1234", @@ -4741,7 +5075,7 @@ "properties": { "additional_metadata": { "additionalProperties": {}, - "description": "AdditionalMetadata is the caller-supplied per-document metadata (stored as\ndocument_metadata).", + "description": "Additional metadata attached to this memory.", "example": { "author": "ada", "doc_version": 3 @@ -4749,64 +5083,54 @@ "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.", - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array", - "uniqueItems": false - }, - "comments_truncated": { - "example": true, - "type": "boolean" - }, "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": "Metadata attached to this memory.", "example": { "department": "finance", "priority": 7 @@ -4814,38 +5138,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" } @@ -4863,7 +5188,7 @@ "properties": { "additional_metadata": { "additionalProperties": {}, - "description": "AdditionalMetadata is the caller-supplied per-document metadata (stored as\ndocument_metadata).", + "description": "Additional metadata attached to this context.", "example": { "author": "ada", "doc_version": 3 @@ -4871,68 +5196,92 @@ "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.", + "example": [ + { + "additional_metadata": { + "author": "ada", + "doc_version": 3 + }, + "app_external_id": "C0123456789", + "app_kind": "slack", + "app_provider": "slack", + "collection": "team_docs", + "comments_truncated": true, + "database": "acme_corp", + "description": "Internal overview of the Project Phoenix rollout.", + "id": "HydraDoc1234", + "metadata": { + "department": "finance", + "priority": 7 + }, + "note": "Superseded by the Q3 rollout plan.", + "sub_tenant_id": "sub_tenant_4567", + "tenant_id": "tenant_1234", + "timestamp": "2026-07-02T10:00:00Z", + "title": "Project Phoenix Overview", + "type": "knowledge" + } + ], "items": { - "additionalProperties": {}, - "type": "object" + "$ref": "#/components/schemas/list.V2SourceItem" }, - "type": "array", - "uniqueItems": false + "type": "array" }, "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" }, "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": "Metadata attached to this context.", "example": { "department": "finance", "priority": 7 @@ -4940,39 +5289,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" } @@ -4986,69 +5335,100 @@ ], "type": "object" }, - "search.ChunkInspectResult": { + "search.AliasExpansionNote": { "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 + "alias": { + "description": "Alias found in the query.", + "type": "string" }, - "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, + "canonical": { + "description": "Canonical name used for the alias.", + "type": "string" + } + }, + "type": "object" + }, + "search.CodeSearchRepoResult": { + "properties": { + "answer": { + "description": "Answer from this repository.", + "type": "string" + }, + "duration_ms": { + "description": "Time spent searching this repository in milliseconds.", + "example": 0.5, + "type": "number" + }, + "error": { + "description": "Error message, empty string on success.", + "example": "", + "type": "string" + }, + "repo": { + "description": "Repository identifier.", + "type": "string" + }, + "status": { + "description": "Current lifecycle or processing state.", + "example": "completed", + "type": "string" + }, + "truncated": { + "description": "Whether the repository answer was shortened.", + "example": true, "type": "boolean" }, - "message": { - "description": "Human-readable result message.", - "example": "Success", + "unsigned": { + "description": "Whether the repository archive was 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": "Whether repository search was selected by the request or automatically.", + "type": "string" + }, + "duration_ms": { + "description": "Time spent on repository search in milliseconds.", + "example": 0.5, + "type": "number" + }, + "reason": { + "description": "Explanation when repository search did not complete successfully.", "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.", + "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": { - "type": "string" + "$ref": "#/components/schemas/search.CodeSearchRepoResult" }, "type": "array", "uniqueItems": false }, - "success": { - "description": "Whether the request succeeded.", - "example": true, - "type": "boolean" + "status": { + "description": "Repository search outcome, `ok`, `not_found`, `error`, `timeout` or `skipped`.", + "example": "completed", + "type": "string" } }, "type": "object" }, "search.GraphContext": { - "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.", + "description": "Graph paths related to the query. Omitted when graph context is disabled.", "properties": { "chunk_id_to_group_ids": { "additionalProperties": { @@ -5069,7 +5449,7 @@ "description": "Scored relation paths relevant to the query, grouped by chunk.", "example": [ { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "combined_context": "Acme Corp deploys HydraDB: Acme Corp deploys HydraDB in production for context retrieval.", "group_id": "grp_1234", "relevancy_score": 0.87, "source_chunk_ids": [ @@ -5079,8 +5459,11 @@ "triplets": [ { "relation": { - "confidence": 0.92, - "predicate": "works_at" + "canonical_predicate": "works_at", + "context": "Ada works at Acme Corp.", + "raw_predicate": "works at", + "source_entity_id": "entity_1a2b", + "target_entity_id": "entity_3c4d" }, "source": { "entity_id": "entity_1a2b", @@ -5106,7 +5489,7 @@ "description": "Scored relation paths ranked by relevance to the query.", "example": [ { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "combined_context": "Acme Corp deploys HydraDB: Acme Corp deploys HydraDB in production for context retrieval.", "group_id": "grp_1234", "relevancy_score": 0.87, "source_chunk_ids": [ @@ -5116,8 +5499,11 @@ "triplets": [ { "relation": { - "confidence": 0.92, - "predicate": "works_at" + "canonical_predicate": "works_at", + "context": "Ada works at Acme Corp.", + "raw_predicate": "works at", + "source_entity_id": "entity_1a2b", + "target_entity_id": "entity_3c4d" }, "source": { "entity_id": "entity_1a2b", @@ -5144,7 +5530,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": "Filter by metadata. Top-level keys accept scalars, lists or `contains`, `contains_any` and `equals` objects. Use bare values under `additional_metadata`. Keys are ANDed.", "example": { "active": true, "additional_metadata": { @@ -5175,33 +5561,33 @@ "search.PathTriplet": { "properties": { "relation": { - "additionalProperties": {}, - "description": "Relation properties including predicate and confidence score.", + "$ref": "#/components/schemas/search.QueryPathRelation", + "description": "Relationship label and supporting context.", "example": { - "confidence": 0.92, - "predicate": "works_at" - }, - "type": "object" + "canonical_predicate": "works_at", + "context": "Ada works at Acme Corp.", + "raw_predicate": "works at", + "source_entity_id": "entity_1a2b", + "target_entity_id": "entity_3c4d" + } }, "source": { - "additionalProperties": {}, + "$ref": "#/components/schemas/search.QueryPathEntity", "description": "Source entity of the relationship.", "example": { "entity_id": "entity_1a2b", "name": "Ada", "type": "person" - }, - "type": "object" + } }, "target": { - "additionalProperties": {}, + "$ref": "#/components/schemas/search.QueryPathEntity", "description": "Target entity of the relationship.", "example": { "entity_id": "entity_3c4d", "name": "Acme Corp", "type": "organization" - }, - "type": "object" + } } }, "type": "object" @@ -5217,10 +5603,130 @@ "QueryByText" ] }, + "search.QueryPathEntity": { + "additionalProperties": false, + "description": "An entity in a query path.", + "properties": { + "entity_id": { + "description": "The entity's stable ID.", + "example": "entity_1a2b", + "type": [ + "string", + "null" + ] + }, + "identifier": { + "description": "External identifier, or null when unknown.", + "example": "Acme Corp", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "The entity's name.", + "type": [ + "string", + "null" + ] + }, + "namespace": { + "description": "Namespace of the entity.", + "example": "organization", + "type": [ + "string", + "null" + ] + }, + "provider": { + "description": "Provider of the supporting context, when known.", + "example": "slack", + "type": "string" + }, + "type": { + "description": "Entity type label.", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "search.QueryPathRelation": { + "additionalProperties": false, + "description": "A relation and its supporting context in a query path.", + "properties": { + "canonical_predicate": { + "description": "Normalized relationship label.", + "example": "works_at", + "type": "string" + }, + "chunk_id": { + "description": "ID of the supporting chunk, or null when unknown.", + "example": "HydraEmbeddings123_0", + "type": [ + "string", + "null" + ] + }, + "context": { + "description": "Text supporting this relationship.", + "example": "Ada joined Acme Corp in 2024 as a staff engineer.", + "type": "string" + }, + "raw_predicate": { + "description": "Original relationship label.", + "example": "is employed by", + "type": "string" + }, + "relationship_id": { + "description": "Stable relationship ID, or null when unknown.", + "example": "rel_1234", + "type": [ + "string", + "null" + ] + }, + "source_entity_id": { + "description": "Source entity ID, or null when unknown.", + "example": "entity_1a2b", + "type": [ + "string", + "null" + ] + }, + "target_entity_id": { + "description": "Target entity ID, or null when unknown.", + "example": "entity_3c4d", + "type": [ + "string", + "null" + ] + }, + "temporal_details": { + "description": "Timing of the relationship, when known.", + "example": "since 2024", + "type": [ + "string", + "null" + ] + }, + "timestamp": { + "description": "Source time in Unix seconds, possibly fractional, or null when unknown.", + "example": 1720000000.5, + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, "search.QueryRequest": { "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.", + "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" }, @@ -5233,7 +5739,28 @@ "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." + "description": "Hybrid search balance. Use auto or a number from 0 to 1.", + "example": "auto", + "oneOf": [ + { + "enum": [ + "auto" + ], + "title": "Automatic", + "type": "string" + }, + { + "maximum": 1, + "minimum": 0, + "title": "Weight", + "type": "number" + } + ] + }, + "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).", @@ -5241,7 +5768,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 as a list of IDs or a mapping of IDs to ranking weights. Use `collection` to select a single collection.", "example": [ "team_docs", "engineering" @@ -5257,6 +5784,7 @@ }, "maxItems": 100, "minItems": 1, + "title": "List of collections", "type": "array" }, { @@ -5271,33 +5799,25 @@ }, "maxProperties": 100, "minProperties": 1, + "title": "Weighted collections", "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.", + "description": "The database to query. Formerly `tenant_id`, which is still accepted.", "example": "acme_corp", "type": "string" }, "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.", + "default": true, + "description": "Include graph context in fast mode. Thinking mode always includes graph context.", "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.", + "description": "Source or memory IDs to search. Returns no matches if none of the IDs match.", "example": [ "HydraDoc1234", "HydraDoc4567" @@ -5305,6 +5825,7 @@ "items": { "type": "string" }, + "maxItems": 200, "type": "array", "uniqueItems": false }, @@ -5314,7 +5835,12 @@ "type": "integer" }, "metadata_filters": { - "$ref": "#/components/schemas/search.MetadataFilters" + "allOf": [ + { + "$ref": "#/components/schemas/search.MetadataFilters" + } + ], + "description": "Use `contains`, `contains_any`, or `equals`. Scalars mean equality; lists mean any match. Use bare values in `additional_metadata`. Keys are ANDed. Max 500 values per list; 64 KiB total." }, "mode": { "$ref": "#/components/schemas/search.RecallMode", @@ -5335,7 +5861,8 @@ "type": "string" }, "query_apps": { - "description": "Whether to include app-aware knowledge retrieval. Applies to knowledge hybrid queries.", + "default": true, + "description": "Enable app-aware knowledge retrieval. Set `false` to disable it while still searching all selected knowledge, including app sources.", "example": true, "type": "boolean" }, @@ -5345,25 +5872,34 @@ "example": "hybrid" }, "query_forceful_relations": { - "description": "Whether to force relation expansion for graph-aware query retrieval. Defaults to true when omitted.", + "default": true, + "description": "In thinking mode, follow relations declared at ingestion and return their chunks in `additional_context`.", "example": true, "type": "boolean" }, "recency_bias": { - "description": "Recency boost applied to ranking. 0 disables it; higher values favour more recent sources.", + "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" }, + "response_format": { + "allOf": [ + { + "$ref": "#/components/schemas/search.ResponseFormat" + } + ], + "description": "What `data` carries. Omit it for the result object with `llm_prompt`; `structured` leaves out `llm_prompt`; `markdown` returns the results as a single Markdown string." + }, "sub_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.", + "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 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.", + "description": "Deprecated: use `collections`, which accepts the same list or weighted object. Do not send both.", "example": [ "sub_tenant_4567", "sub_tenant_8901" @@ -5379,6 +5915,7 @@ }, "maxItems": 100, "minItems": 1, + "title": "List of collections", "type": "array" }, { @@ -5393,38 +5930,42 @@ }, "maxProperties": 100, "minProperties": 1, + "title": "Weighted collections", "type": "object" } ], "x-deprecated": "true", "x-deprecated-since": "2.0.1" }, - "temporal_intent": { - "$ref": "#/components/schemas/search.TemporalIntentOverride", - "example": { - "duration_to_now": true, - "mode": "thinking" - } - }, - "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" - }, "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).", + "default": true, + "description": "Resolve time-based questions using dated facts. Defaults to `true`.", "example": true, "type": "boolean" }, "tenant_id": { "deprecated": true, - "description": "deprecated: use database", + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" }, + "titles": { + "description": "Exact document titles to search within, matched case-insensitively and ORed. Intersected with `ids` when both are sent.", + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + }, "type": { - "$ref": "#/components/schemas/search.SourceType", - "description": "Corpus to query: knowledge, memory, or all." + "allOf": [ + { + "$ref": "#/components/schemas/search.SourceType" + } + ], + "default": "knowledge", + "description": "Context to search, `knowledge`, `memory` or `all`." } }, "type": "object" @@ -5442,25 +5983,45 @@ "RecallModeAuto" ] }, + "search.ResponseFormat": { + "description": "What the /query response `data` carries: omitted (result object with `llm_prompt`), `structured` or `markdown`.", + "enum": [ + "", + "structured", + "markdown" + ], + "type": "string", + "x-enum-varnames": [ + "ResponseFormatBoth", + "ResponseFormatStructured", + "ResponseFormatMarkdown" + ] + }, "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" + "description": "Summary of the graph path, or null when unavailable.", + "example": "Acme Corp deploys HydraDB: Acme Corp deploys HydraDB in production for context retrieval.", + "type": [ + "string", + "null" + ] }, "group_id": { - "description": "Unique identifier for this relation group.", + "description": "Relation group ID, or null when unavailable.", "example": "grp_1234", - "type": "string" + "type": [ + "string", + "null" + ] }, "relevancy_score": { - "description": "Relevance score for this item against the query.", + "description": "Relevance of this path to the query.", "example": 0.87, "type": "number" }, "source_chunk_ids": { - "description": "IDs of the chunks that contribute to this relation path.", + "description": "Supporting chunk IDs, or null when unavailable.", "example": [ "HydraEmbeddings123_0", "HydraEmbeddings123_1" @@ -5468,16 +6029,21 @@ "items": { "type": "string" }, - "type": "array", - "uniqueItems": false + "type": [ + "array", + "null" + ] }, "triplets": { "description": "Knowledge-graph triplets that make up this relation path.", "example": [ { "relation": { - "confidence": 0.92, - "predicate": "works_at" + "canonical_predicate": "works_at", + "context": "Ada works at Acme Corp.", + "raw_predicate": "works at", + "source_entity_id": "entity_1a2b", + "target_entity_id": "entity_3c4d" }, "source": { "entity_id": "entity_1a2b", @@ -5500,180 +6066,8 @@ }, "type": "object" }, - "search.SourceFact": { - "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", - "type": "string" - }, - "relation": { - "type": "string" - }, - "relationship_id": { - "description": "Unique identifier for this relationship instance.", - "example": "rel_1234", - "type": "string" - }, - "source_id": { - "example": "HydraDoc1234", - "type": "string" - }, - "synced_at": { - "example": 1, - "type": "integer" - }, - "thread_id": { - "type": "string" - } - }, - "type": "object" - }, - "search.SourceFilterInfo": { - "description": "SourceFilter reports what the source layer did for this request.", - "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": { - "example": true, - "type": "boolean" - }, - "truncated": { - "example": true, - "type": "boolean" - } - }, - "type": "object" - }, - "search.SourceInfo": { - "properties": { - "additional_metadata": { - "additionalProperties": {}, - "description": "Per-document free-form metadata.", - "example": { - "author": "ada", - "doc_version": 3 - }, - "type": "object" - }, - "app_external_id": { - "description": "Provider-assigned identifier for this source (e.g. Slack channel ID).", - "example": "C0123456789", - "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", - "type": "string" - }, - "app_provider": { - "description": "Provider name for app-sourced items (e.g. `slack`, `github`).", - "example": "slack", - "type": "string" - }, - "collection": { - "description": "Collection this source belongs to. Canonical name; mirrors the deprecated `sub_tenant_id` alias.", - "example": "team_docs", - "type": "string" - }, - "description": { - "description": "Human-readable description of the source.", - "example": "Internal overview of the Project Phoenix rollout.", - "type": "string" - }, - "id": { - "description": "Unique identifier for this resource.", - "example": "HydraDoc1234", - "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", - "type": "string" - }, - "title": { - "description": "Title or name of the source.", - "example": "Project Phoenix Overview", - "type": "string" - }, - "type": { - "description": "Source content category (e.g. `knowledge`, `memory`).", - "example": "knowledge", - "type": "string" - }, - "url": { - "description": "URL to the original source, if available.", - "example": "https://docs.hydradb.com/phoenix", - "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.", + "description": "Context to search, `knowledge`, `memory` or `all`.", "enum": [ "knowledge", "memory", @@ -5687,14 +6081,15 @@ ] }, "search.TemporalDuration": { - "description": "TemporalDuration is the computed event-duration answer, when resolved.", + "description": "Duration between two dated facts.", "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.", + "description": "Whether the duration is an estimate because a date has month or year precision.", "example": true, "type": "boolean" }, "days": { + "description": "Number of days between the dates.", "example": 1, "type": "integer" }, @@ -5710,10 +6105,11 @@ } }, "from_date": { + "description": "Start date used to calculate the duration.", "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).", + "description": "Confidence that the dated facts answer the question, from 0 to 1.", "example": 0.5, "type": "number" }, @@ -5729,6 +6125,7 @@ } }, "to_date": { + "description": "End date used to calculate the duration.", "type": "string" } }, @@ -5742,28 +6139,33 @@ "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).", + "description": "Precision of the date, `day`, `month` or `year`.", "type": "string" }, "event_end": { + "description": "End of the event in Unix seconds, when known.", "example": 1, "type": "integer" }, "event_start": { + "description": "Start of the event in Unix seconds, when known.", "example": 1, "type": "integer" }, "evidence_phrase": { - "description": "EvidencePhrase is the verbatim source phrase the dates were resolved\nfrom (e.g. \"today\", \"two weeks ago\").", + "description": "Original phrase supporting the date.", "type": "string" }, "fact_type": { + "description": "Category of the dated fact.", "type": "string" }, "object": { + "description": "Object of the dated fact.", "type": "string" }, "relation": { + "description": "Relationship label of the dated fact.", "type": "string" }, "relationship_id": { @@ -5772,6 +6174,7 @@ "type": "string" }, "source_id": { + "description": "ID of the supporting context.", "example": "HydraDoc1234", "type": "string" }, @@ -5781,76 +6184,7 @@ "type": "string" }, "subject": { - "type": "string" - } - }, - "type": "object" - }, - "search.TemporalFilterInfo": { - "description": "TemporalFilter reports what the temporal layer did for this request.", - "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.", - "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.", - "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.", - "properties": { - "cutoff": { - "type": "string" - }, - "duration_to_now": { - "example": true, - "type": "boolean" - }, - "event_phrases": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": false - }, - "mode": { - "example": "thinking", - "type": "string" - }, - "window_end": { - "type": "string" - }, - "window_start": { + "description": "Subject of the dated fact.", "type": "string" } }, @@ -5860,7 +6194,7 @@ "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.", + "description": "Additional metadata attached to the context.", "example": { "author": "ada", "doc_version": 3 @@ -5906,7 +6240,7 @@ }, "metadata": { "additionalProperties": {}, - "description": "Schema-backed tenant metadata attached to the source.", + "description": "Metadata attached to the context.", "example": { "department": "finance", "priority": 7 @@ -5914,7 +6248,7 @@ "type": "object" }, "relevancy_score": { - "description": "Relevance score for this item against the query.", + "description": "Relevance of this chunk to the query.", "example": 0.87, "type": "number" }, @@ -5940,7 +6274,7 @@ }, "sub_tenant_id": { "deprecated": true, - "description": "deprecated: use collection", + "description": "Deprecated: use `collection`.", "example": "sub_tenant_4567", "type": "string", "x-deprecated": "true" @@ -5954,10 +6288,17 @@ "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).", - "example": "The user is a senior engineer onboarding to the platform.", + "description": "Map of chunk IDs to related content declared at ingestion.", "type": "object" }, + "alias_expansions": { + "description": "Aliases expanded to canonical names that contributed to these results.", + "items": { + "$ref": "#/components/schemas/search.AliasExpansionNote" + }, + "type": "array", + "uniqueItems": false + }, "chunks": { "description": "Retrieved and ranked chunks from the knowledge store or memories.", "example": [ @@ -5993,8 +6334,34 @@ "type": "array", "uniqueItems": false }, + "code_search": { + "allOf": [ + { + "$ref": "#/components/schemas/search.CodeSearchResult" + } + ], + "description": "Repository search results, when available.", + "example": { + "duration_ms": 0.5, + "repos": [ + { + "duration_ms": 0.5, + "error": "", + "status": "completed", + "truncated": true, + "unsigned": true + } + ], + "status": "completed" + } + }, "graph_context": { - "$ref": "#/components/schemas/search.GraphContext", + "allOf": [ + { + "$ref": "#/components/schemas/search.GraphContext" + } + ], + "description": "Graph paths related to the query, when requested.", "example": { "chunk_id_to_group_ids": { "HydraEmbeddings123_0": [ @@ -6003,7 +6370,7 @@ }, "chunk_relations": [ { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "combined_context": "Acme Corp deploys HydraDB: Acme Corp deploys HydraDB in production for context retrieval.", "group_id": "grp_1234", "relevancy_score": 0.87, "source_chunk_ids": [ @@ -6013,8 +6380,11 @@ "triplets": [ { "relation": { - "confidence": 0.92, - "predicate": "works_at" + "canonical_predicate": "works_at", + "context": "Ada works at Acme Corp.", + "raw_predicate": "works at", + "source_entity_id": "entity_1a2b", + "target_entity_id": "entity_3c4d" }, "source": { "entity_id": "entity_1a2b", @@ -6032,7 +6402,7 @@ ], "query_paths": [ { - "combined_context": "Acme Corp deploys HydraDB in production for context retrieval.", + "combined_context": "Acme Corp deploys HydraDB: Acme Corp deploys HydraDB in production for context retrieval.", "group_id": "grp_1234", "relevancy_score": 0.87, "source_chunk_ids": [ @@ -6042,8 +6412,11 @@ "triplets": [ { "relation": { - "confidence": 0.92, - "predicate": "works_at" + "canonical_predicate": "works_at", + "context": "Ada works at Acme Corp.", + "raw_predicate": "works at", + "source_entity_id": "entity_1a2b", + "target_entity_id": "entity_3c4d" }, "source": { "entity_id": "entity_1a2b", @@ -6061,66 +6434,9 @@ ] } }, - "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 + "llm_prompt": { + "description": "Results rendered as Markdown for a model. Omitted with `response_format=structured`; `data` is a string with `response_format=markdown`.", + "type": "string" }, "temporal_duration": { "$ref": "#/components/schemas/search.TemporalDuration", @@ -6137,126 +6453,32 @@ }, "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" + "chunk_id": "HydraEmbeddings123_0", + "event_end": 1, + "event_start": 1, + "relationship_id": "rel_1234", + "source_id": "HydraDoc1234", + "status": "completed" + } + } }, - "extra_context_ids": { - "description": "IDs of adjacent chunks pulled in as surrounding context.", + "temporal_facts": { + "description": "Dated facts matching this query when temporal reasoning is enabled.", "example": [ - "HydraEmbeddings123_2", - "HydraEmbeddings123_3" + { + "chunk_id": "HydraEmbeddings123_0", + "event_end": 1, + "event_start": 1, + "relationship_id": "rel_1234", + "source_id": "HydraDoc1234", + "status": "completed" + } ], "items": { - "type": "string" + "$ref": "#/components/schemas/search.TemporalFact" }, "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" @@ -6264,7 +6486,7 @@ "sources.MemoryDeleteResponse": { "properties": { "deleted_count": { - "description": "Total number of items successfully deleted.", + "description": "Number of contexts deleted. `0` means no ID matched anything to delete.", "example": 1, "type": "integer" }, @@ -6274,7 +6496,7 @@ "type": "string" }, "results": { - "description": "Per-item results.", + "description": "One result per requested ID.", "example": [ { "deleted": true, @@ -6290,13 +6512,13 @@ }, "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).", + "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 memory items deleted.", + "description": "Number of memories deleted.", "example": 1, "type": "integer" } @@ -6306,17 +6528,17 @@ "sources.SourceDeleteResultItem": { "properties": { "deleted": { - "description": "Whether this specific item was deleted.", + "description": "Whether this context was deleted.", "example": true, "type": "boolean" }, "error": { - "description": "Error message for this item, empty string on success.", + "description": "Why this ID was not deleted; empty string on success.", "example": "", "type": "string" }, "id": { - "description": "Unique identifier for this resource.", + "description": "The requested context ID.", "example": "HydraDoc1234", "type": "string" } @@ -6326,17 +6548,17 @@ "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).", + "description": "Collection the contexts belong to. 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 by the TenantAliases middleware before\nbinding so TenantID is always populated.", + "description": "Database the contexts belong to. Required. Formerly `tenant_id`; the alias is still accepted.", "example": "acme_corp", "type": "string" }, "ids": { - "description": "IDs of the sources or memories to delete.", + "description": "The IDs of the contexts to delete.", "example": [ "HydraDoc1234", "HydraDoc4567" @@ -6349,23 +6571,24 @@ }, "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": { - "description": "Bucket to delete from: `knowledge` (default) or `memory`.", + "description": "Corpus to delete from, `knowledge`, `memory` or `all` for both. Defaults to `knowledge`.", "enum": [ "knowledge", - "memory" + "memory", + "all" ], "example": "knowledge", "type": "string" @@ -6386,8 +6609,12 @@ "tenants.CustomPropertyDefinition": { "properties": { "data_type": { - "$ref": "#/components/schemas/tenants.MilvusDataType", - "description": "Milvus data type for this metadata field.", + "allOf": [ + { + "$ref": "#/components/schemas/tenants.MetadataDataType" + } + ], + "description": "Data type of the metadata field, for example `VARCHAR`.", "example": "VARCHAR" }, "enable_dense_embedding": { @@ -6432,6 +6659,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -6470,6 +6698,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -6485,12 +6714,12 @@ "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.", + "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" }, @@ -6504,7 +6733,8 @@ }, "type": "object" }, - "tenants.MilvusDataType": { + "tenants.MetadataDataType": { + "description": "Data type of a metadata field. `ARRAY` cannot be declared on a new field: for several values, declare `VARCHAR` and store them comma-joined.", "enum": [ "BOOL", "INT8", @@ -6531,6 +6761,45 @@ "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, + "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": "object" + }, "tenants.SubTenantIdsResponse": { "properties": { "collections": { @@ -6552,7 +6821,7 @@ }, "sub_tenant_ids": { "deprecated": true, - "description": "Deprecated alias for `collections`.", + "description": "Deprecated: use `collections`. Same value.", "example": [ "sub_tenant_4567", "sub_tenant_8901" @@ -6586,6 +6855,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -6596,12 +6866,12 @@ "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.", + "description": "Name of the database to create. Formerly `tenant_id`, which is still accepted.", "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.", + "description": "Database-level metadata fields for exact-match filtering and semantic or BM25 search. Field names cannot change after creation.", "example": [ { "data_type": "VARCHAR", @@ -6630,14 +6900,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" }, @@ -6648,43 +6918,6 @@ }, "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": "object" - }, "tenants.TenantDeleteResponse": { "properties": { "database": { @@ -6704,6 +6937,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -6742,7 +6976,7 @@ }, "failed_tenant_ids": { "deprecated": true, - "description": "Deprecated alias for `failed_databases`.", + "description": "Deprecated: use `failed_databases`. Same value.", "example": [ { "database": "acme_corp", @@ -6764,7 +6998,7 @@ }, "tenant_ids": { "deprecated": true, - "description": "Deprecated alias for `databases`.", + "description": "Deprecated: use `databases`. Same value.", "example": [ "tenant_1234", "tenant_5678" @@ -6787,6 +7021,7 @@ "type": "string" }, "fields": { + "description": "Fields declared in the database metadata schema.", "example": [ { "data_type": "VARCHAR", @@ -6805,6 +7040,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "acme_corp", "type": "string", "x-deprecated": "true" @@ -6815,7 +7051,7 @@ "tenants.TenantMetadataSchemaUpdateRequest": { "properties": { "add_fields": { - "description": "New metadata schema fields to add to the database. Additive only — no deletes, renames, or type changes.", + "description": "New metadata fields to add to the database schema. Additive only: no deletes, renames or type changes.", "example": [ { "data_type": "VARCHAR", @@ -6861,6 +7097,7 @@ }, "tenant_id": { "deprecated": true, + "description": "Deprecated: use `database`.", "example": "tenant_1234", "type": "string", "x-deprecated": "true" @@ -6891,7 +7128,7 @@ "type": "integer" }, "created_at": { - "description": "RFC3339 timestamp when this item was created.", + "description": "When this delivery was created (RFC 3339).", "example": "2026-07-02T10:00:00Z", "type": "string" }, @@ -6906,14 +7143,20 @@ "type": "string" }, "error_code": { - "description": "Machine-readable error code, empty string on success.", - "example": "", - "type": "string" + "description": "Error code, or null when no error occurred.", + "example": null, + "type": [ + "string", + "null" + ] }, "error_message": { - "description": "Human-readable error description, empty string on success.", - "example": "", - "type": "string" + "description": "Error message, or null when no error occurred.", + "example": null, + "type": [ + "string", + "null" + ] }, "event_type": { "description": "Event that triggered this delivery (e.g. `indexing.status_changed`).", @@ -6936,7 +7179,7 @@ "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.", + "description": "The endpoint this delivery was sent to: the URL registered when the delivery was created.", "type": "string" } }, @@ -6945,7 +7188,7 @@ "webhooks.DeliveryListResponse": { "properties": { "count": { - "description": "Total number of items returned.", + "description": "Number of deliveries returned.", "example": 12, "type": "integer" }, @@ -6957,8 +7200,8 @@ "created_at": "2026-07-02T10:00:00Z", "delivery_id": "dlv_9f8e7d6c", "doc_id": "HydraDoc1234", - "error_code": "", - "error_message": "", + "error_code": null, + "error_message": null, "event_type": "indexing.status_changed", "indexing_status": "completed", "status": "completed", @@ -6972,9 +7215,12 @@ "uniqueItems": false }, "next_cursor": { - "description": "Opaque pagination cursor for the next page; null or absent when no more pages.", - "example": "eyJvZmZzZXQiOjUwfQ==", - "type": "string" + "description": "Cursor for the next page, or null on the last page.", + "example": null, + "type": [ + "string", + "null" + ] } }, "type": "object" @@ -7002,7 +7248,7 @@ "webhooks.SigningSecretRequest": { "properties": { "signing_secret": { - "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=`, 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 for signing webhook deliveries, at least 16 characters. Omit it or send null to generate one.", "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", "type": "string" } @@ -7022,7 +7268,7 @@ "type": "string" }, "signing_secret": { - "description": "Secret used to sign webhook payloads. Deliveries carry `X-HydraDB-Signature: sha256=`, 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": "Signing secret. Store it securely to verify webhook deliveries.", "example": "whsec_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_SIGNING_KEY", "type": "string" } @@ -7032,7 +7278,7 @@ "webhooks.WebhookDeleteResponse": { "properties": { "deleted": { - "description": "Whether this specific item was deleted.", + "description": "Whether the webhook was deleted.", "example": true, "type": "boolean" }, @@ -7068,9 +7314,12 @@ "type": "boolean" }, "url": { - "description": "Registered endpoint URL that receives webhook event deliveries.", + "description": "Registered webhook URL, or null when none is registered.", "example": "https://docs.hydradb.com/phoenix", - "type": "string" + "type": [ + "string", + "null" + ] } }, "type": "object" @@ -7078,203 +7327,94 @@ "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=`, 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=`, 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" - }, - "handler.resourceUpdateReq": { - "properties": { - "acl": { - "description": "Who can read this resource's objects: emails, `group::`, `domain:`, or `[\"__public__\"]`. `[]` allows nobody. Provider permissions, where read, take over at the next sync.", + "description": "Event types to subscribe to (e.g. `[\"indexing.status_changed\"]`).", + "example": [ + "indexing.status_changed" + ], "items": { "type": "string" }, "type": "array", "uniqueItems": false }, - "custom_instructions": { - "description": "Ingestion and indexing instructions for this resource, replacing the connector's. Omit to leave unchanged; `\"\"` clears back to the connector's. Up to 4000 characters, applied from the next sync.", - "type": "string" - } - }, - "type": "object" - }, - "handler.planCapView": { - "description": "The plan limit that is currently stopping syncs.", - "properties": { - "message": { - "description": "Human-readable explanation of the limit, the same message the ingest endpoints return.", - "example": "Success", - "type": "string" + "generate_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" }, - "meter": { - "description": "Which limit was reached: `tokens` or `storage`.", + "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" }, - "plan": { - "description": "Plan the organization is on.", + "url": { + "description": "Endpoint URL to deliver webhook events to.", + "example": "https://docs.hydradb.com/phoenix", "type": "string" } }, "type": "object" }, - "handler.resourceUpdateResponse": { + "webhooks.WebhookRegisterResponse": { "properties": { - "acl": { - "description": "The access rule as stored. Present only when the request set `acl`.", + "event_types": { + "description": "Event types to subscribe to (e.g. `[\"indexing.status_changed\"]`).", + "example": [ + "indexing.status_changed" + ], "items": { "type": "string" }, "type": "array", "uniqueItems": false }, - "connector_id": { - "description": "Connector this resource belongs to.", - "example": "conn_abc123", - "type": "string" - }, - "custom_instructions": { - "description": "The resource's instructions as stored; `\"\"` means they were cleared. Present only when the request set them.", + "message": { + "description": "Human-readable result message.", + "example": "Success", "type": "string" }, - "custom_instructions_updated": { - "description": "`true` when this request set or cleared the resource's `custom_instructions`.", + "registered": { + "description": "Whether a webhook is registered for this API key.", "example": true, "type": "boolean" }, - "resource_id": { - "description": "Resource identifier from the Discover endpoint.", - "example": "C0123456789", - "type": "string" - } - }, - "type": "object" - }, - "handler.connectorPauseResponse": { - "properties": { - "connector_id": { - "description": "Connector this resource belongs to.", - "example": "conn_abc123", + "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" }, - "paused": { - "description": "`true` after a pause, `false` after a resume.", + "signing_secret_configured": { + "description": "Whether a signing secret has been configured for payload verification.", "example": true, "type": "boolean" }, - "paused_at": { - "description": "When the connector was paused (RFC 3339). Absent after a resume.", + "url": { + "description": "Registered endpoint URL that receives webhook event deliveries.", + "example": "https://docs.hydradb.com/phoenix", "type": "string" } }, "type": "object" }, - "handler.connectorLimitView": { - "description": "The plan's limit on the number of connectors.", + "webhooks.WebhookTestResponse": { "properties": { - "count": { - "description": "Number of connectors the organization has.", - "example": 12, - "type": "integer" - }, - "limit": { - "description": "Number of connectors the plan allows.", - "example": 1, - "type": "integer" + "delivered": { + "description": "Whether the test delivery was accepted by the endpoint.", + "example": true, + "type": "boolean" }, "message": { - "description": "Human-readable explanation, safe to show to users as is.", + "description": "Human-readable result message.", "example": "Success", "type": "string" }, - "plan": { - "description": "Plan the organization is on.", - "type": "string" + "status_code": { + "description": "HTTP status from the test request, or null when no response was received.", + "example": 200, + "type": [ + "integer", + "null" + ] } }, "type": "object" @@ -7283,7 +7423,7 @@ "securitySchemes": { "BearerAuth": { "bearerFormat": "API key", - "description": "API key sent as a Bearer token: \"Bearer prefix.secret\"", + "description": "Send your API key as Authorization: Bearer \u003cyour_api_key\u003e.", "scheme": "bearer", "type": "http" } @@ -7298,7 +7438,7 @@ "email": "support@hydradb.com", "name": "HydraDB Support" }, - "description": "HydraDB Application API — knowledge ingestion, search, and memory management.", + "description": "The HydraDB API: ingest context, query it, and manage databases, connectors and webhooks.", "license": { "name": "Proprietary" }, @@ -7319,6 +7459,14 @@ "example": "slack", "type": "string" } + }, + { + "description": "Comma-separated extras. health adds each connector's health rollup; any other value is rejected with 400.", + "in": "query", + "name": "include", + "schema": { + "type": "string" + } } ], "responses": { @@ -7332,6 +7480,16 @@ }, "description": "OK" }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, "500": { "content": { "application/json": { @@ -7459,6 +7617,11 @@ "description": "Not Found" } }, + "security": [ + { + "BearerAuth": [] + } + ], "summary": "List supported providers, or describe one in detail", "tags": [ "connectors" @@ -7584,7 +7747,7 @@ "x-fern-sdk-method-name": "get" }, "patch": { - "description": "Update a connector's settings in place. Only the fields you send change. `sync_interval_seconds` sets the sync cadence within the range returned in the response (`0` resets to the provider default). `credentials` reconnects the connector with a full credential set, keeping its id, resources and sync cursors. `custom_instructions` replaces the connector-level ingestion instructions (`\"\"` clears them) from the next sync. Every field is validated before anything is written, so an invalid value changes nothing. Credentials are saved before the other settings, so a server error after that point can leave only the credentials updated.", + "description": "Update the supplied connector settings. Use `credentials` to reconnect, `sync_interval_seconds` to change cadence, or `custom_instructions` to change ingestion instructions.", "parameters": [ { "description": "Connector ID", @@ -7665,7 +7828,7 @@ }, "/connectors/{id}/configure": { "post": { - "description": "Activate resources and start a sync. Resources not in the list are left as they are. A listed resource keeps its sync position, instructions and access rule when those are omitted, but its `name`, `resource_type`, `database` and `collection` are set to what you send.", + "description": "Activate the listed resources and start a sync. Unlisted resources remain unchanged. Send each listed resource's name, type, database and collection.", "parameters": [ { "description": "Connector ID", @@ -7720,6 +7883,16 @@ }, "description": "Not Found" }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/handler.ErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + }, "500": { "content": { "application/json": { @@ -7852,7 +8025,7 @@ }, "/connectors/{id}/pause": { "post": { - "description": "Stop scheduled syncs for a connector until you resume it. A sync already running stops at its next step. Each resource resumes from its last saved position, so nothing is skipped. Returns `409` when the connector is already paused.", + "description": "Pause scheduled syncs. An active sync stops at its next step. Resume continues from the saved position. Returns `409` if already paused.", "parameters": [ { "description": "Connector ID", @@ -7980,7 +8153,7 @@ "x-fern-sdk-method-name": "list_resources" }, "post": { - "description": "Add one resource to a connector. Returns `409` when the connector already has a resource with this `resource_id`, and changes nothing. To change one, use `POST /connectors/{id}/configure` or `PATCH /connectors/{id}/resources/{resource_id}`.", + "description": "Add a resource to the connector. An existing `resource_id` returns `409`. Use the resource PATCH endpoint to change its settings.", "parameters": [ { "description": "Connector ID", @@ -8009,7 +8182,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/connectors.Resource" + "$ref": "#/components/schemas/handler.resourceCreateResponse" } } }, @@ -8159,7 +8332,7 @@ "x-fern-sdk-method-name": "delete_resource" }, "patch": { - "description": "Update one configured resource's settings without re-sending the rest of it. `custom_instructions` sets this resource's ingestion instructions (`\"\"` clears them back to the connector's), applied from the next sync. `acl` sets who can read the resource's objects and applies to already-synced objects immediately. For providers whose permissions HydraDB reads, the provider's own restrictions take over again at the next sync; your rule applies whenever the provider reports the resource as public or its permissions cannot be read. Send at least one of the two; omitted fields are left unchanged.", + "description": "Update resource instructions or access rules. Send at least one field. Omitted fields keep their values; provider permissions apply again at the next sync.", "parameters": [ { "description": "Connector ID", @@ -8250,7 +8423,7 @@ }, "/connectors/{id}/resume": { "post": { - "description": "Put a paused connector back on its schedule, due for a sync right away. Each resource continues from where it stopped, so changes made during the pause are picked up. Returns `409` when it is not paused.", + "description": "Resume a paused connector and schedule a sync immediately. Each resource continues from its saved position. Returns `409` if the connector is not paused.", "parameters": [ { "description": "Connector ID", @@ -8380,7 +8553,7 @@ }, "/connectors/{id}/sync": { "post": { - "description": "Start a sync now instead of waiting for the schedule. If a sync is already running, it continues and `run_id` is empty. Returns `409` while the connector is paused.", + "description": "Start a sync now. If one is running, it continues and `run_id` is empty. Returns `409` while paused or when no resources are active.", "parameters": [ { "description": "Connector ID", @@ -8460,10 +8633,10 @@ }, "/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.", + "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": "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`.", + "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": { @@ -8535,146 +8708,7 @@ } } }, - "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": [ - { - "BearerAuth": [] - } - ], - "summary": "Delete documents or memories", - "tags": [ - "context" - ], - "x-fern-sdk-group-name": "context", - "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.", - "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", - "name": "id", - "schema": { - "example": "HydraDoc1234", - "type": "string" - } - }, - { - "description": "Chunk IDs to return. Repeated (chunk_ids=a&chunk_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": "Corpus type: 'knowledge' or 'memory'", - "in": "query", - "name": "type", - "schema": { - "enum": [ - "knowledge", - "memory" - ], - "type": "string" - } - }, - { - "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&acl=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-search_ChunkInspectResult" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/handler.ErrorResponse" - } - } - }, - "description": "Bad Request" + "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": [ @@ -8682,68 +8716,77 @@ "BearerAuth": [] } ], - "summary": "Get chunk text", + "summary": "Delete documents or memories", "tags": [ "context" ], "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "chunks" + "x-fern-sdk-method-name": "delete" } }, "/context/ingest": { "post": { - "description": "Ingest knowledge documents or memories for a tenant.", + "description": "Ingest documents, app knowledge or memories using a multipart form. Select `knowledge` or `memory` with `type`. Poll `GET /context/status` with the returned IDs.", "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:`, a bare email, `group::`, `domain:`, or `__public__`) restricting who may retrieve it; omit it to leave the document unrestricted, and send an empty list to restrict it to nobody. A malformed principal rejects the whole request with 400.", + "description": "App knowledge entries to ingest, encoded as a JSON array.", "title": "app_knowledge", "type": "string" }, "collection": { + "description": "Collection to write to. Omit it to use the database's default collection.", "title": "collection", "type": "string" }, "database": { + "description": "Database to write to. Required.", "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.", + "description": "Per-file metadata for uploaded documents, encoded as JSON.", "title": "document_metadata", "type": "string" }, "documents": { - "format": "binary", + "description": "Files to ingest into the knowledge corpus.", + "items": { + "format": "binary", + "type": "string" + }, "title": "documents", - "type": "string" + "type": "array" }, "graph_payload": { + "description": "Your own graph, encoded as JSON and keyed by source ID.", "title": "graph_payload", "type": "string" }, "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.", + "description": "Memories to ingest, encoded as a JSON array. Use `type=memory`.", "title": "memories", "type": "string" }, "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": { - "default": "knowledge", + "description": "Corpus to ingest into, `knowledge` or `memory`.", "enum": [ "knowledge", "memory" @@ -8753,6 +8796,7 @@ }, "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" } @@ -8764,7 +8808,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 <= 16 KiB, additional_metadata <= 1 KiB. | App-knowledge items as a JSON array (type=knowledge). Per item: metadata <= 16 KiB, additional_metadata <= 1 KiB, optional acl principal list (PRO-1684). | Memory items as a JSON array (type=memory). Per item: metadata <= 16 KiB, additional_metadata <= 1 KiB. | Optional bring-your-own-graph payload as JSON", + "description": "A multipart form containing the database, collection and content to ingest.", "required": true }, "responses": { @@ -8806,7 +8850,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": { @@ -8834,10 +8878,10 @@ }, "/context/inspect": { "get": { - "description": "Fetch a previously ingested source's content, inferred content, and a downloadable URL.", + "description": "Return the stored content of one context, its enrichment, and a time-limited download URL.", "parameters": [ { - "description": "Source ID", + "description": "The ID of the context to inspect.", "in": "query", "name": "id", "required": true, @@ -8847,7 +8891,7 @@ } }, { - "description": "Database (canonical name for the tenant scope)", + "description": "Database the context belongs to. Required.", "in": "query", "name": "database", "required": true, @@ -8857,7 +8901,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": { @@ -8866,7 +8910,8 @@ } }, { - "description": "Deprecated alias for database", + "deprecated": true, + "description": "Deprecated: use `database`.", "in": "query", "name": "tenant_id", "schema": { @@ -8877,7 +8922,8 @@ } }, { - "description": "Deprecated alias for collection", + "deprecated": true, + "description": "Deprecated: use `collection`.", "in": "query", "name": "sub_tenant_id", "schema": { @@ -8888,7 +8934,7 @@ } }, { - "description": "Presigned URL expiry in seconds", + "description": "Lifetime of `presigned_url` in seconds, from 60 to 604800 (7 days). Default 3600.", "in": "query", "name": "expiry_seconds", "schema": { @@ -8897,7 +8943,7 @@ } }, { - "description": "Fetch mode", + "description": "What to return: `content` (the stored content and enrichment), `url` (a download URL only) or `both`. Default `both`.", "in": "query", "name": "mode", "schema": { @@ -8906,7 +8952,7 @@ } }, { - "description": "Principals to answer as (PRO-1684 document ACLs): the source must be visible to them, or the response is 404. Repeated (acl=a&acl=b) or comma-separated. Omit for no ACL scoping.", + "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": { @@ -8965,7 +9011,7 @@ }, "/context/list": { "post": { - "description": "List knowledge sources or memories (id + metadata) for a tenant.", + "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": { @@ -9014,10 +9060,10 @@ }, "/context/relations": { "get": { - "description": "Return knowledge-graph relations for a tenant or a single source.", + "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 (canonical name for the tenant scope)", + "description": "Database to read. Required.", "in": "query", "name": "database", "required": true, @@ -9027,7 +9073,7 @@ } }, { - "description": "Collection (canonical name for the sub-tenant scope)", + "description": "Collection to read. Defaults to the database's default collection.", "in": "query", "name": "collection", "schema": { @@ -9036,7 +9082,8 @@ } }, { - "description": "Deprecated alias for database", + "deprecated": true, + "description": "Deprecated: use `database`.", "in": "query", "name": "tenant_id", "schema": { @@ -9047,7 +9094,8 @@ } }, { - "description": "Deprecated alias for collection", + "deprecated": true, + "description": "Deprecated: use `collection`.", "in": "query", "name": "sub_tenant_id", "schema": { @@ -9058,7 +9106,7 @@ } }, { - "description": "Source ID (omit for database-wide relations)", + "description": "`context_id` of the context whose relations to return. Omit it for relations across the whole collection.", "in": "query", "name": "id", "schema": { @@ -9067,7 +9115,7 @@ } }, { - "description": "Corpus type: 'knowledge' or 'memory'", + "description": "Corpus to read, `knowledge` or `memory`. Defaults to `knowledge`.", "in": "query", "name": "type", "schema": { @@ -9079,7 +9127,7 @@ } }, { - "description": "Max relations to return", + "description": "Maximum number of relation groups to return, from `1` to `10000`. Default `5000`.", "in": "query", "name": "limit", "schema": { @@ -9088,7 +9136,7 @@ } }, { - "description": "Pagination cursor", + "description": "The `next_cursor` from the previous page, passed back unchanged. Omit it for the first page.", "in": "query", "name": "cursor", "schema": { @@ -9096,7 +9144,7 @@ } }, { - "description": "Principals to answer as (PRO-1684 document ACLs): only relations attributable to sources they may see are returned. Repeated (acl=a&acl=b) or comma-separated. Omit for no ACL 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": { @@ -9145,10 +9193,10 @@ }, "/context/status": { "get": { - "description": "Return the processing status for one or more source IDs.", + "description": "Return the processing status of one or more contexts by ID, including contexts synced by a connector.", "parameters": [ { - "description": "Single source ID", + "description": "One context ID. Combined with `ids`.", "in": "query", "name": "id", "schema": { @@ -9157,7 +9205,7 @@ } }, { - "description": "One or more source IDs", + "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": { @@ -9172,7 +9220,7 @@ } }, { - "description": "Database (canonical name for the tenant scope)", + "description": "Database the contexts belong to. Required.", "in": "query", "name": "database", "required": true, @@ -9182,7 +9230,7 @@ } }, { - "description": "Collection (canonical name for the sub-tenant scope)", + "description": "Collection the contexts belong to. Omit it to use the database's default collection.", "in": "query", "name": "collection", "schema": { @@ -9191,7 +9239,8 @@ } }, { - "description": "Deprecated alias for database", + "deprecated": true, + "description": "Deprecated: use `database`.", "in": "query", "name": "tenant_id", "schema": { @@ -9202,7 +9251,8 @@ } }, { - "description": "Deprecated alias for collection", + "deprecated": true, + "description": "Deprecated: use `collection`.", "in": "query", "name": "sub_tenant_id", "schema": { @@ -9248,12 +9298,151 @@ "x-fern-sdk-method-name": "status" } }, + "/context/subgraph": { + "get": { + "description": "Return the contexts reachable from one context through context-level relations, up to `depth` hops, with their relations and surrounding graph.", + "parameters": [ + { + "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, + "schema": { + "example": "acme_corp", + "type": "string" + } + }, + { + "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": "Corpus to read, `knowledge` or `memory`. Defaults to `knowledge`.", + "in": "query", + "name": "type", + "schema": { + "enum": [ + "knowledge", + "memory" + ], + "type": "string" + } + }, + { + "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; 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": { + "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": "subgraph" + } + }, "/context/{id}/metadata": { "patch": { - "description": "Merge/upsert database_metadata and additional_metadata for one source. collection is required.", + "description": "Update one context without re-ingesting it: merge its metadata and additional metadata, or replace its access-control list. `database` and `collection` are required.", "parameters": [ { - "description": "Source ID", + "description": "`context_id` of the context to update.", "in": "path", "name": "id", "required": true, @@ -9330,10 +9519,10 @@ }, "/context/{id}/subgraph": { "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": "Get the connected subgraph of one context.", "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=.", + "description": "Context ID to start from. Use the query form for IDs containing a slash.", "in": "path", "name": "id", "required": true, @@ -9343,7 +9532,7 @@ } }, { - "description": "Database (canonical name for the tenant scope)", + "description": "Database containing the context.", "in": "query", "name": "database", "required": true, @@ -9353,7 +9542,7 @@ } }, { - "description": "Collection (canonical name for the sub-tenant scope)", + "description": "Collection containing the context.", "in": "query", "name": "collection", "schema": { @@ -9362,7 +9551,8 @@ } }, { - "description": "Deprecated alias for database", + "deprecated": true, + "description": "Deprecated: use `database`.", "in": "query", "name": "tenant_id", "schema": { @@ -9373,7 +9563,8 @@ } }, { - "description": "Deprecated alias for collection", + "deprecated": true, + "description": "Deprecated: use `collection`.", "in": "query", "name": "sub_tenant_id", "schema": { @@ -9384,11 +9575,10 @@ } }, { - "description": "Corpus type: 'knowledge' or 'memory'", + "description": "Corpus to read, `knowledge` or `memory`. Defaults to `knowledge`.", "in": "query", "name": "type", "schema": { - "default": "knowledge", "enum": [ "knowledge", "memory" @@ -9419,7 +9609,7 @@ } }, { - "description": "Principals to answer as (document ACLs): the subgraph contains only items they may see, filtered at every hop. Repeated (acl=a&acl=b) or comma-separated. Omit for no ACL scoping.", + "description": "Principals allowed to read the returned contexts. Omit for no additional scoping.", "in": "query", "name": "acl", "schema": { @@ -9462,8 +9652,7 @@ "tags": [ "context" ], - "x-fern-sdk-group-name": "context", - "x-fern-sdk-method-name": "subgraph" + "x-fern-ignore": true } }, "/databases": { @@ -9650,8 +9839,8 @@ } }, "/databases/collections": { - "get": { - "description": "List all collections for a given database", + "delete": { + "description": "Permanently remove one collection and all of its data from a database. The database itself is left intact.", "parameters": [ { "description": "Database identifier", @@ -9662,6 +9851,16 @@ "example": "acme_corp", "type": "string" } + }, + { + "description": "Collection identifier", + "in": "query", + "name": "collection", + "required": true, + "schema": { + "example": "team_docs", + "type": "string" + } } ], "responses": { @@ -9669,7 +9868,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantIdsResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantDeleteResponse" } } }, @@ -9711,18 +9910,18 @@ "BearerAuth": [] } ], - "summary": "List collections", + "summary": "Delete a collection", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "collections" + "x-fern-sdk-method-name": "deleteCollection" }, - "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.", + "get": { + "description": "List all collections for a given database", "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.", + "description": "Database identifier", "in": "query", "name": "database", "required": true, @@ -9730,16 +9929,6 @@ "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" - } } ], "responses": { @@ -9747,7 +9936,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantDeleteResponse" + "$ref": "#/components/schemas/handler.Envelope-tenants_SubTenantIdsResponse" } } }, @@ -9789,12 +9978,12 @@ "BearerAuth": [] } ], - "summary": "Delete a collection", + "summary": "List collections", "tags": [ "database-management" ], "x-fern-sdk-group-name": "databases", - "x-fern-sdk-method-name": "deleteCollection" + "x-fern-sdk-method-name": "collections" } }, "/databases/stats": { @@ -9939,10 +10128,10 @@ }, "/databases/{database}/metadata-schema": { "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": "Get the database metadata schema.", "parameters": [ { - "description": "Database identifier", + "description": "Database whose metadata schema to read.", "in": "path", "name": "database", "required": true, @@ -10007,7 +10196,7 @@ "x-fern-sdk-method-name": "get_metadata_schema" }, "patch": { - "description": "Add new metadata schema fields to an existing database. Additive only — existing fields cannot be deleted or retyped.", + "description": "Add fields to the database metadata schema. Existing fields cannot be deleted, renamed or retyped.", "parameters": [ { "description": "Database identifier", @@ -10098,7 +10287,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": { @@ -10107,7 +10296,7 @@ } } }, - "description": "Feedback submission", + "description": "Send `feedback`, `ground_truth`, or both. A request with neither is rejected with `400`.", "required": true }, "responses": { @@ -10167,7 +10356,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": "Search a database for ranked chunks and graph context. Filter with `metadata_filters`, `ids` or `titles`. Results include `llm_prompt` unless `response_format` changes it.", "requestBody": { "content": { "application/json": { @@ -10176,7 +10365,7 @@ } } }, - "description": "Unified query request", + "description": "The query and how to scope and rank it.", "required": true }, "responses": { @@ -10297,7 +10486,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": { @@ -10500,7 +10689,7 @@ }, "/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.", + "description": "Remove the webhook signing secret.", "responses": { "200": { "content": { @@ -10536,7 +10725,7 @@ "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.", + "description": "Set or generate the secret used to sign webhook deliveries.", "requestBody": { "content": { "application/json": { @@ -10629,80 +10818,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": [ @@ -10718,4 +10833,4 @@ "x-fern-default": "2" } ] -} +} \ No newline at end of file diff --git a/api-reference/v2/sdks.mdx b/api-reference/v2/sdks.mdx index 71c42030..8985e026 100644 --- a/api-reference/v2/sdks.mdx +++ b/api-reference/v2/sdks.mdx @@ -1,20 +1,20 @@ --- -title: "SDKs – Python and Node" +title: "SDKs: Python and TypeScript" description: "Official Python and TypeScript/Node.js SDKs for the HydraDB API." --- -The SDKs wrap every endpoint in the [API Reference](/api-reference/v2) with typed methods and IDE autocomplete. They automatically set the `API-Version: 2` header on every request - you do not need to send it manually. +The SDKs wrap every endpoint in the [API Reference](/api-reference/v2) with typed methods and IDE autocomplete. They automatically set the `API-Version: 2` header on every request, so you do not need to send it manually. ## Installation -The SDKs ship as major-version bumps of the same packages used for v1. Pin to `>=2.0.0` to use the latest method surface. +Install version 2 of the Python or TypeScript SDK: ```bash Python SDK -pip install "hydradb-sdk>=2,<3" +pip install "hydradb-sdk>=2.1.7,<3" ``` ```bash TypeScript / Node.js -npm install @hydradb/sdk@^2 +npm install @hydradb/sdk@^2.1.7 ``` @@ -42,52 +42,44 @@ const client = new HydraDBClient({ ``` - -**Python:** Both synchronous (`HydraDB`) and asynchronous (`AsyncHydraDB`) clients are available. They share an identical surface - choose based on your application's concurrency model. - - -## Versioning - -The SDKs automatically include `API-Version: 2` on every outbound request. Server-side routing resolves to the matching routes; the response includes an `X-API-Version: 2` header echoing the resolved version. - -You can verify which version your client is using by inspecting the response headers in any SDK call. +**Python:** Both synchronous (`HydraDB`) and asynchronous (`AsyncHydraDB`) clients are available. They expose the same methods; use `AsyncHydraDB` with `await`. ## Naming conventions -The REST API uses **snake_case** for all request and response fields, and both SDKs preserve those field names for request/response objects. TypeScript only camelCases multi-word **method names**. +The REST API and Python SDK use **snake_case** for request and response fields. The TypeScript SDK uses **camelCase** for multi-word method names and fields in typed request and response objects. | | Method naming | Parameter field naming | Example | |---|---|---|---| -| **Raw HTTP / cURL** | - | snake_case | `database`, `collection`, `query_by` | +| **Raw HTTP / cURL** | N/A | snake_case | `database`, `collection`, `query_by` | | **Python SDK** | snake_case | snake_case | method: `query()`, params: `database`, `query_by` | -| **TypeScript SDK** | camelCase for multi-word methods | snake_case | method: `query()`, params: `database`, `query_by` | +| **TypeScript SDK** | camelCase for multi-word methods | camelCase | method: `query()`, params: `database`, `queryBy` | - -**Use snake_case request fields in TypeScript examples too** (e.g., `client.context.list({ database: "...", page_size: 50 })`). The SDK sends the same names on the wire, matching the OpenAPI schema. +For example, use `client.context.list({ database: "...", pageSize: 50 })` in TypeScript. The SDK sends `page_size` to the API and drops unrecognized typed fields, including a caller-supplied `page_size`. + +JSON strings (`documentMetadata`, `memories`, `appKnowledge`) and free-form maps (`metadataFilters` and a connector resource update's `body`) are sent as written. Use REST field names inside them, such as `additional_metadata` or `custom_instructions`, and preserve your own metadata keys. The Python SDK also uses snake_case throughout (e.g., `client.context.list(database="...", page_size=50)`). `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. - - -Your IDE's autocomplete and type checking work directly off the API contract - if a field is optional in the API, it's optional in the SDK. ## SDK method structure -SDK methods are grouped under three top-level namespaces - one per `/api-reference/v2` group: +SDK methods are grouped under top-level namespaces, one per `/api-reference/v2` group: | URL prefix | SDK group | Purpose | |---|---|---| -| `/context/*` and `/context` | `client.context` | Ingest, status, fetch, list, delete, graph relations | +| `/context/*` and `/context` | `client.context` | Ingest, status, fetch, list, delete, metadata updates, graph relations, subgraph | | `/query` | `client.query` | Unified retrieval over knowledge, memories, or both | -| `/databases/*` and `/databases` | `client.databases` | Create, list, delete, status, collections, stats. Legacy `/tenants/*` paths remain as deprecated aliases. | - +| `/feedback` | `client.feedback` | Feedback on query results | +| `/databases/*` and `/databases` | `client.databases` | Create, list, delete, status, collections, stats, collection deletion, metadata schema. Legacy `/tenants/*` paths remain as deprecated aliases. | +| `/connectors/*` and `/connectors` | `client.connectors` | Create, configure, sync, and manage app connectors. `GET /connectors/providers` is `client.list_providers()`. | +| `/webhooks/indexing*` | `client.webhooks` | Register, inspect, test, and delete the indexing webhook; list and retry deliveries | ## Method reference ### Context -`client.context.*` covers every flow around content lifecycle - document uploads, app sources, memories, polling, fetching, listing, deletion, and graph inspection. +`client.context.*` manages your stored context: document uploads, app sources, memories, polling, fetching, listing, deletion, and graph inspection. | Method | Endpoint | |---|---| @@ -97,6 +89,8 @@ SDK methods are grouped under three top-level namespaces - one per `/api-refer | `client.context.list()` | `POST /context/list` | | `client.context.delete()` | `DELETE /context` | | `client.context.relations()` | `GET /context/relations` | +| `client.context.update_source_metadata()` | `PATCH /context/{id}/metadata` | +| `client.context.subgraph()` | `GET /context/{id}/subgraph` | ### Query @@ -106,6 +100,12 @@ A single method covers all retrieval. Pick `type` (`"knowledge"`, `"memory"`, `" |---|---| | `client.query()` | `POST /query` | +### Feedback + +| Method | Endpoint | +|---|---| +| `client.feedback.submit()` | `POST /feedback` | + ### Databases | Method | Endpoint | @@ -116,12 +116,50 @@ A single method covers all retrieval. Pick `type` (`"knowledge"`, `"memory"`, `" | `client.databases.status()` | `GET /databases/status` | | `client.databases.collections()` | `GET /databases/collections` | | `client.databases.stats()` | `GET /databases/stats` | +| `client.databases.delete_collection()` | `DELETE /databases/collections` | +| `client.databases.update_metadata_schema()` | `PATCH /databases/{database}/metadata-schema` | +| `client.databases.get_metadata_schema()` | `GET /databases/{database}/metadata-schema` | -The method-reference tables above use the Python (snake_case) method names. TypeScript keeps the same method names but camelCases any that are multi-word (for example, the connector method `list_resources()` in Python is `listResources()` in TypeScript). Request parameters stay snake_case in both SDKs. +### Connectors + +| Method | Endpoint | +|---|---| +| `client.list_providers()` | `GET /connectors/providers` | +| `client.connectors.create()` | `POST /connectors` | +| `client.connectors.discover()` | `GET /connectors/{id}/discover` | +| `client.connectors.configure()` | `POST /connectors/{id}/configure` | +| `client.connectors.status()` | `GET /connectors/{id}/status` | +| `client.connectors.list()` | `GET /connectors` | +| `client.connectors.get()` | `GET /connectors/{id}` | +| `client.connectors.list_resources()` | `GET /connectors/{id}/resources` | +| `client.connectors.sync()` | `POST /connectors/{id}/sync` | +| `client.connectors.pause()` | `POST /connectors/{id}/pause` | +| `client.connectors.resume()` | `POST /connectors/{id}/resume` | +| `client.connectors.update()` | `PATCH /connectors/{id}` | +| `client.connectors.update_resource_acl()` | `PATCH /connectors/{id}/resources/{resource_id}` | +| `client.connectors.create_resource()` | `POST /connectors/{id}/resources` | +| `client.connectors.delete_resource()` | `DELETE /connectors/{id}/resources/{resource_id}` | +| `client.connectors.delete()` | `DELETE /connectors/{id}` | + +### Webhooks + +| Method | Endpoint | +|---|---| +| `client.webhooks.register()` | `POST /webhooks/indexing` | +| `client.webhooks.get()` | `GET /webhooks/indexing` | +| `client.webhooks.delete()` | `DELETE /webhooks/indexing` | +| `client.webhooks.test()` | `POST /webhooks/indexing/test` | +| `client.webhooks.list_deliveries()` | `GET /webhooks/indexing/deliveries` | +| `client.webhooks.get_delivery()` | `GET /webhooks/indexing/deliveries/{delivery_id}` | +| `client.webhooks.retry_delivery()` | `POST /webhooks/indexing/deliveries/{delivery_id}/retry` | +| `client.webhooks.set_signing_secret()` | `POST /webhooks/indexing/signing-secret` | +| `client.webhooks.clear_signing_secret()` | `DELETE /webhooks/indexing/signing-secret` | + +The tables above use the Python method names. TypeScript camelCases multi-word ones (`list_resources()` is `listResources()`). ## Migrating from v1 -The v2 SDKs are a deliberate consolidation. A few common v1 → v2 method swaps: +Common method changes from v1 to v2: | v1 | v2 | |---|---| @@ -149,24 +187,25 @@ The v1 SDK methods remain available on the `<2.0.0` releases of `hydradb-sdk` / ### Create a database -A database is an isolated workspace. Most organizations create one database total, with collections for users or teams. See [Multi-Tenant](/essentials/v2/multi-tenant) for the full model. +A database isolates context for a customer, environment, or product. See [Multi-tenancy](/essentials/v2/multi-tenant) for guidance on choosing database and collection scopes. ```python Python SDK response = client.databases.create( database="my_first_database", database_metadata_schema=[ - {"name": "department", "data_type": "VARCHAR", "enable_match": True}, + {"name": "department", "data_type": "VARCHAR"}, ], ) ``` ```python Python (Async) import asyncio - async def create_database(): - return await async_client.databases.create(database="my_first_database") - + return await async_client.databases.create( + database="my_first_database", + database_metadata_schema=[{"name": "department", "data_type": "VARCHAR"}], + ) response = asyncio.run(create_database()) ``` @@ -174,7 +213,7 @@ response = asyncio.run(create_database()) const response = await client.databases.create({ database: "my_first_database", databaseMetadataSchema: [ - { name: "department", dataType: "VARCHAR", enableMatch: true }, + { name: "department", dataType: "VARCHAR" }, ], }); ``` @@ -188,13 +227,7 @@ import time while True: status = client.databases.status(database="my_first_database") - infra = status.data.infra - if ( - infra.scheduler_status - and infra.graph_status - and infra.vectorstore_status.knowledge - and infra.vectorstore_status.memories - ): + if status.data.infra.ready_for_ingestion: break time.sleep(2) ``` @@ -203,13 +236,8 @@ while (true) { const status = await client.databases.status({ database: "my_first_database", }); - const { schedulerStatus, graphStatus, vectorstoreStatus } = status.data.infra; - if ( - schedulerStatus && - graphStatus && - vectorstoreStatus.knowledge && - vectorstoreStatus.memories - ) break; + // Response fields are optional in the TypeScript types. + if (status.data?.infra?.readyForIngestion) break; await new Promise((r) => setTimeout(r, 2000)); } ``` @@ -227,6 +255,7 @@ with open("contract.pdf", "rb") as f1, open("policy.pdf", "rb") as f2: result = client.context.ingest( type="knowledge", database="my_first_database", + collection="company_docs", documents=[ ("contract.pdf", f1, "application/pdf"), ("policy.pdf", f2, "application/pdf"), @@ -249,6 +278,7 @@ with open("contract.pdf", "rb") as f1, open("policy.pdf", "rb") as f2: const result = await client.context.ingest({ type: "knowledge", database: "my_first_database", + collection: "company_docs", documents: [ { path: "contract.pdf", filename: "contract.pdf", contentType: "application/pdf" }, { path: "policy.pdf", filename: "policy.pdf", contentType: "application/pdf" }, @@ -285,10 +315,11 @@ result = client.context.ingest( collection="user_alex", memories=json.dumps([ { + "id": "alex_preferences", "text": "User prefers detailed technical explanations and dark mode", "infer": True, "user_name": "Alex", - "metadata": {"team": "engineering"}, + "metadata": {"department": "engineering"}, "additional_metadata": {"source": "onboarding"}, } ]), @@ -301,10 +332,11 @@ const result = await client.context.ingest({ collection: "user_alex", memories: JSON.stringify([ { + id: "alex_preferences", text: "User prefers detailed technical explanations and dark mode", infer: true, user_name: "Alex", - metadata: { team: "engineering" }, + metadata: { department: "engineering" }, additional_metadata: { source: "onboarding" }, }, ]), @@ -312,52 +344,75 @@ const result = await client.context.ingest({ ``` - **`infer` defaults to `false`.** Set `infer: true` for conversational content where you want HydraDB to extract implicit preferences. Use `infer: false` for content that should be stored verbatim. - -### Verify processing + + +### Wait for indexing -Poll `context.status` with the IDs from the ingest response: +Wait until the documents and memories are indexed before querying them. Check each collection separately, using the IDs supplied at ingestion or returned in `data.results`. ```python Python SDK -status = client.context.status( - database="my_first_database", - ids=["contract_q4", "policy_2025"], -) +import time + +def wait_for_indexing(collection, ids): + while True: + result = client.context.status( + database="my_first_database", collection=collection, ids=ids, + ) + statuses = [item.indexing_status for item in result.data.statuses] + if any(status == "errored" for status in statuses): + raise RuntimeError("Indexing failed; check the status response for details.") + if len(statuses) == len(ids) and all(status == "completed" for status in statuses): + return + time.sleep(2) + +wait_for_indexing("company_docs", ["contract_q4", "policy_2025"]) +wait_for_indexing("user_alex", ["alex_preferences"]) ``` ```typescript TypeScript SDK -const status = await client.context.status({ - database: "my_first_database", - ids: ["contract_q4", "policy_2025"], -}); +async function waitForIndexing(collection: string, ids: string[]) { + while (true) { + const result = await client.context.status({ + database: "my_first_database", collection, ids, + }); + const statuses = result.data?.statuses?.map((item) => item.indexingStatus) ?? []; + if (statuses.some((status) => status === "errored")) { + throw new Error("Indexing failed; check the status response for details."); + } + if (statuses.length === ids.length && statuses.every((status) => status === "completed")) return; + await new Promise((resolve) => setTimeout(resolve, 2000)); + } +} + +await waitForIndexing("company_docs", ["contract_q4", "policy_2025"]); +await waitForIndexing("user_alex", ["alex_preferences"]); ``` -Wait until each `indexing_status` is `completed` (or `graph_creation` if you don't need full graph traversal). See [Context Overview](/api-reference/v2/endpoint/sources-overview) for the full status pipeline. - ### Query -A single method covers every retrieval pattern - switch `type` and `query_by` to control behavior: +A single method covers every retrieval pattern: switch `type` and `query_by` to control behavior: ```python Python SDK # Knowledge over documents knowledge = client.query( database="my_first_database", - query="What are the pricing tiers?", + collection="company_docs", + query="What is our refund policy?", type="knowledge", query_by="hybrid", mode="thinking", graph_context=True, ) -# Personalized — searches knowledge AND user memories together +# Shared knowledge and this user's memories personalized = client.query( database="my_first_database", - collection="user_alex", - query="What are the pricing tiers, and how should I explain them to this user?", + collections=["company_docs", "user_alex"], + query="What is our refund policy, and how should I explain it to this user?", type="all", query_by="hybrid", mode="thinking", @@ -366,6 +421,7 @@ personalized = client.query( # Exact-phrase BM25 search exact = client.query( database="my_first_database", + collection="company_docs", query="ERROR_429 rate limit", type="knowledge", query_by="text", @@ -376,18 +432,19 @@ exact = client.query( // Knowledge over documents const knowledge = await client.query({ database: "my_first_database", - query: "What are the pricing tiers?", + collection: "company_docs", + query: "What is our refund policy?", type: "knowledge", queryBy: "hybrid", mode: "thinking", graphContext: true, }); -// Personalized — searches knowledge AND user memories together +// Shared knowledge and this user's memories const personalized = await client.query({ database: "my_first_database", - collection: "user_alex", - query: "What are the pricing tiers, and how should I explain them to this user?", + collections: ["company_docs", "user_alex"], + query: "What is our refund policy, and how should I explain it to this user?", type: "all", queryBy: "hybrid", mode: "thinking", @@ -396,6 +453,7 @@ const personalized = await client.query({ // Exact-phrase BM25 search const exact = await client.query({ database: "my_first_database", + collection: "company_docs", query: "ERROR_429 rate limit", type: "knowledge", queryBy: "text", @@ -404,7 +462,7 @@ const exact = await client.query({ ``` -For the full parameter reference, see [Query – Overview](/api-reference/v2/endpoint/query-overview). +For the full parameter reference, see [Query](/api-reference/v2/endpoint/query). ### Browse, fetch, and inspect @@ -413,6 +471,7 @@ For the full parameter reference, see [Query – Overview](/api-reference/v2/end # List knowledge sources with pagination sources = client.context.list( database="my_first_database", + collection="company_docs", type="knowledge", page=1, page_size=50, @@ -421,13 +480,15 @@ sources = client.context.list( # Inspect graph relationships for a specific source relations = client.context.relations( database="my_first_database", - id="doc_12345", + collection="company_docs", + id="contract_q4", ) # Inspect original source content or get a presigned URL file = client.context.inspect( database="my_first_database", - id="doc_12345", + collection="company_docs", + id="contract_q4", mode="url", ) @@ -435,13 +496,15 @@ file = client.context.inspect( client.context.delete( type="knowledge", database="my_first_database", - ids=["doc_12345"], + collection="company_docs", + ids=["contract_q4"], ) ``` ```typescript TypeScript SDK // List knowledge sources with pagination const sources = await client.context.list({ database: "my_first_database", + collection: "company_docs", type: "knowledge", page: 1, pageSize: 50, @@ -450,13 +513,15 @@ const sources = await client.context.list({ // Inspect graph relationships for a specific source const relations = await client.context.relations({ database: "my_first_database", - id: "doc_12345", + collection: "company_docs", + id: "contract_q4", }); // Inspect original source content or get a presigned URL const file = await client.context.inspect({ database: "my_first_database", - id: "doc_12345", + collection: "company_docs", + id: "contract_q4", mode: "url", }); @@ -464,14 +529,15 @@ const file = await client.context.inspect({ await client.context.delete({ type: "knowledge", database: "my_first_database", - ids: ["doc_12345"], + collection: "company_docs", + ids: ["contract_q4"], }); ``` ## Response envelope -All responses are wrapped in a consistent envelope: +All responses, except connector endpoints and `PATCH /databases/{database}/metadata-schema`, are wrapped in a consistent envelope: ```json { @@ -489,25 +555,6 @@ All responses are wrapped in a consistent envelope: The SDKs return the full envelope. Read the payload from the `data` field (e.g. `response.data`); on failure the SDK raises a typed exception instead of returning an envelope with `error` populated. -## Type safety - - -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 -- **Enum-typed values** for `type`, `query_by`, `operator` and `mode` - - -The SDKs provide exact type parity with the API specification: - -- **Request parameters** - every field documented in the API reference is reflected in method signatures -- **Response objects** - return types match the JSON schema for each endpoint -- **Error types** - exception structures mirror error response formats -- **Nested objects** - complex parameters and responses keep their full structure - ## Error handling Both SDKs throw exceptions for non-2xx responses. Error objects expose the envelope's `error` payload: @@ -528,17 +575,12 @@ except NotFoundError: pass except ApiError as exc: if exc.status_code == 429: - # Rate limit — retry with backoff + # Rate limit: retry with backoff pass else: raise ``` - -The Python SDK raises a typed exception per status from `hydra_db.errors`. Each subclasses `ApiError` and carries `status_code`, `headers`, and the parsed response `body`, from which you can read `body["error"]["code"]`. - -Not every status has its own class - `401` and `429` do not. Catch the base `ApiError` and branch on `status_code`, as above, whenever you need to handle those. - ```typescript TypeScript SDK import { HydraDBError } from "@hydradb/sdk"; @@ -550,12 +592,13 @@ try { }); } catch (error) { if (error instanceof HydraDBError) { - const code = error.body?.error?.code; + // error.body is typed `unknown`; it holds the raw snake_case error envelope. + const code = (error.body as { error?: { code?: string } } | undefined)?.error?.code; if (code === "DATABASE_NOT_FOUND") { // Handle missing database } else if (error.statusCode === 429) { - // Rate limit — retry with backoff + // Rate limit: retry with backoff } else { throw error; } @@ -566,22 +609,16 @@ try { ``` -For the full list of error codes and retry patterns, see [Error Responses](/api-reference/v2/error-responses). - -## IDE-driven discovery - -Whether you're using TypeScript, Python, VS Code, PyCharm, or any modern IDE, the workflow is the same: +The Python SDK raises a typed exception per status from `hydra_db.errors`. Each subclasses `ApiError` and carries `status_code`, `headers`, and the parsed response `body`, from which you can read `body["error"]["code"]`. -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 +Not every status has its own class: `401` and `429` do not. Catch the base `ApiError` and branch on `status_code`, as above, whenever you need to handle those. -This works because the SDKs are fully typed with comprehensive parameter docs sourced from the OpenAPI spec. +For the full list of error codes and retry patterns, see [Error Responses](/api-reference/v2/error-responses). ## Related sections -- [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 -- [Query](/essentials/v2/query) - conceptual overview of `POST /query` -- [Knowledge](/essentials/v2/knowledge) and [Memories](/essentials/v2/memories) - ingestion deep-dives +- [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 +- [Query](/essentials/v2/query): conceptual overview of `POST /query` +- [Knowledge](/essentials/v2/knowledge) and [Memories](/essentials/v2/memories): ingestion deep-dives diff --git a/docs.json b/docs.json index 0b67f57b..58d52b5c 100644 --- a/docs.json +++ b/docs.json @@ -161,13 +161,14 @@ "pages": [ "api-reference/v2/endpoint/tenants-overview", "api-reference/v2/endpoint/create-tenant", - "api-reference/v2/endpoint/update-metadata-schema", - "api-reference/v2/endpoint/list-tenants", - "api-reference/v2/endpoint/delete-tenant", "api-reference/v2/endpoint/tenant-status", + "api-reference/v2/endpoint/list-tenants", + "api-reference/v2/endpoint/tenant-stats", + "api-reference/v2/endpoint/get-metadata-schema", + "api-reference/v2/endpoint/update-metadata-schema", "api-reference/v2/endpoint/list-sub-tenants", "api-reference/v2/endpoint/delete-collection", - "api-reference/v2/endpoint/tenant-stats" + "api-reference/v2/endpoint/delete-tenant" ] }, { @@ -177,12 +178,12 @@ "api-reference/v2/endpoint/sources-overview", "api-reference/v2/endpoint/ingest-context", "api-reference/v2/endpoint/source-status", - "api-reference/v2/endpoint/fetch-content", "api-reference/v2/endpoint/list-documents", + "api-reference/v2/endpoint/fetch-content", "api-reference/v2/endpoint/update-source-metadata", - "api-reference/v2/endpoint/delete-source", "api-reference/v2/endpoint/source-relations", - "api-reference/v2/endpoint/subgraph" + "api-reference/v2/endpoint/subgraph", + "api-reference/v2/endpoint/delete-source" ] }, { @@ -213,13 +214,16 @@ "group": "Webhooks", "public": true, "pages": [ + "api-reference/v2/endpoint/webhooks-overview", "api-reference/v2/endpoint/register-webhook", "api-reference/v2/endpoint/get-webhook", "api-reference/v2/endpoint/delete-webhook", "api-reference/v2/endpoint/test-webhook", "api-reference/v2/endpoint/list-webhook-deliveries", "api-reference/v2/endpoint/get-webhook-delivery", - "api-reference/v2/endpoint/retry-webhook-delivery" + "api-reference/v2/endpoint/retry-webhook-delivery", + "api-reference/v2/endpoint/set-webhook-signing-secret", + "api-reference/v2/endpoint/disable-webhook-signing" ] }, { @@ -441,6 +445,26 @@ "source": "/docs/capabilities/embeddings", "destination": "/essentials/v2/semantic-search", "permanent": true + }, + { + "source": "/api-reference/v2/endpoint/patch-metadata-schema", + "destination": "/api-reference/v2/endpoint/update-metadata-schema", + "permanent": true + }, + { + "source": "/api-reference/v2/endpoint/ingest", + "destination": "/api-reference/v2/endpoint/ingest-context", + "permanent": true + }, + { + "source": "/api-reference/v2/endpoint/connector-status", + "destination": "/api-reference/v2/endpoint/get-connector-status", + "permanent": true + }, + { + "source": "/api-reference/v2/endpoint/list-collections", + "destination": "/api-reference/v2/endpoint/list-sub-tenants", + "permanent": true } ] } diff --git a/essentials/v2/access-control.mdx b/essentials/v2/access-control.mdx index 75be9aba..3113ee70 100644 --- a/essentials/v2/access-control.mdx +++ b/essentials/v2/access-control.mdx @@ -369,7 +369,7 @@ Check the principal forms on both sides. `group:slack:C0123` on the document onl - [Connectors](/essentials/v2/connectors): syncing app data, and per-resource ACL rules - [App Sources](/essentials/v2/app-sources): the ingest shape that carries `acl` - [Query](/essentials/v2/query): the `acl` field alongside every other retrieval parameter -- [Metadata](/essentials/v2/metadata): filtering by attributes, a different question from permission +- [Metadata and Filters](/essentials/v2/metadata): narrow results by metadata - [Multi-tenancy](/essentials/v2/multi-tenant): 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 - [Update Connector Resource (API Reference)](/api-reference/v2/endpoint/update-connector-resource): the per-resource rule diff --git a/essentials/v2/api-results.mdx b/essentials/v2/api-results.mdx index 5b52b65a..bb698aa0 100644 --- a/essentials/v2/api-results.mdx +++ b/essentials/v2/api-results.mdx @@ -1,178 +1,47 @@ --- title: "How to Use API Results" -description: "Turn a query response into an LLM prompt." +description: "Pass retrieved context to a model or work with individual results." --- -`POST /query` returns structured JSON. Before you can pass it to an LLM, you need to convert the retrieval payload into a plain string. This page shows how. +`POST /query` retrieves context for your question. By default, `data.llm_prompt` contains the question, matching passages, source references, and citation instructions in one Markdown string. Pass this string to your model and ask it to answer using the supplied context. -The same pattern works regardless of whether you set `type: "knowledge"`, `type: "memory"`, or `type: "all"` - the response shape is identical. + + ---- - -## 1. What the response looks like - -The SDKs return the full response envelope from `POST /query`; the retrieval payload is on its `data` field (e.g. `result.data`). Raw HTTP responses use the same envelope, with the payload under `data`. The `build_string` / `buildString` helper accepts either the full envelope or just the `data` payload, so you can pass the SDK return value to it directly. - -The retrieval payload has the same core shape regardless of `type` or `query_by`: - -```json -{ - "chunks": [ - { - "chunk_uuid": "doc-001_chunk_0", - "source_title": "my_document.pdf", - "chunk_content": "Text content of the retrieved chunk...", - "relevancy_score": 1.09, - "extra_context_ids": ["ctx-id-1"], - "additional_metadata": { - "author": "Support Team" - } - } - ], - "graph_context": { - "query_paths": [ - { - "triplets": [ - { - "source": { "name": "EntityA" }, - "target": { "name": "EntityB" }, - "relation": { - "canonical_predicate": "DEPENDS_ON", - "context": "reason text", - "temporal_details": "2024-01" - } - } - ] - } - ], - "chunk_id_to_group_ids": { - "doc-001_chunk_0": ["group-1"] - }, - "chunk_relations": [ - { - "group_id": "group-1", - "triplets": [ - { - "source": { "name": "EntityA" }, - "target": { "name": "EntityB" }, - "relation": { - "canonical_predicate": "DEPENDS_ON", - "context": "reason text" - } - } - ] - } - ] - }, - "additional_context": { - "ctx-id-1": { - "source_title": "related_doc.pdf", - "chunk_content": "Extra related content..." - } - } -} -``` +## Choose a response format -Four things matter for prompt construction: +Set `response_format` on the query request. The result is under `data` in the response envelope. -- **`chunks`** - the primary retrieval output. Ranked by relevance; preserve the order HydraDB returns. -- **`graph_context.query_paths`** - entity traversal paths derived from your query. Useful for relational reasoning. See [Context Graphs](/essentials/v2/context-graphs). -- **`graph_context.chunk_relations`** + **`chunk_id_to_group_ids`** - per-chunk graph relations grouped by `group_id`, so you can attach the right triplets to each chunk. -- **`additional_context`** - a map keyed by `chunk_uuid`. When a chunk includes `extra_context_ids`, use those IDs to look up related chunks here. +| `response_format` | Returned `data` | +|---|---| +| Omitted, the default | Object containing `llm_prompt` and individual results. | +| `"markdown"` | The same Markdown text returned directly as `data`. | +| `"structured"` | Object containing individual results, without `llm_prompt`. | -The raw object has too much noise for an LLM - IDs, timestamps, metadata. Section 2 shows how to convert it into a clean string. - ---- + + + + -## 2. Transforming the response into LLM context +## Send context to your model -Use `build_string` / `buildString` from the SDK. It takes any `POST /query` result and returns a formatted plain string. - - -```python Python SDK -from hydra_db import HydraDB -from hydra_db.helpers import build_string - -client = HydraDB(token="YOUR_API_KEY") - -result = client.query( - database="your-database", - collection="your-collection", - query="How does authentication work?", - type="knowledge", - query_by="hybrid", - max_results=5, - mode="fast", - graph_context=True, -) - -context = build_string(result) -``` -```typescript TypeScript SDK -import { HydraDBClient } from "@hydradb/sdk"; -import { buildString } from "@hydradb/sdk/helpers"; - -const client = new HydraDBClient({ token: process.env.HYDRA_DB_API_KEY }); - -const result = await client.query({ - database: "your-database", - collection: "your-collection", - query: "How does authentication work?", - type: "knowledge", - queryBy: "hybrid", - maxResults: 5, - mode: "fast", - graphContext: true, -}); - -const context = buildString(result); -``` -```python Python SDK (Async) -import asyncio - -from hydra_db import AsyncHydraDB -from hydra_db.helpers import build_string - -client = AsyncHydraDB(token="YOUR_API_KEY") - - -async def main(): - result = await client.query( - database="your-database", - collection="your-collection", - query="How does authentication work?", - type="knowledge", - query_by="hybrid", - max_results=5, - mode="fast", - graph_context=True, - ) - - context = build_string(result) - return context - - -asyncio.run(main()) -``` - - ---- - -## 3. Feeding the context into your LLM +In SDK 2.1.7 or later, `build_string(result)` in Python and `buildString(result)` in TypeScript read `data.llm_prompt` into the `context` variable below. The model call then includes `context` and the original `question` in the same message. ```python Python SDK +import os from hydra_db import HydraDB from hydra_db.helpers import build_string from openai import OpenAI -hydra = HydraDB(token="YOUR_HYDRA_DB_API_KEY") -openai_client = OpenAI(api_key="YOUR_OPENAI_API_KEY") +hydra = HydraDB(token=os.environ["HYDRA_DB_API_KEY"]) +openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) question = "How does authentication work?" result = hydra.query( database="your-database", + collection="your-collection", query=question, type="knowledge", query_by="hybrid", @@ -210,6 +79,7 @@ const question = "How does authentication work?"; const result = await hydra.query({ database: "your-database", + collection: "your-collection", query: question, type: "knowledge", queryBy: "hybrid", @@ -237,20 +107,21 @@ console.log(completion.choices[0].message.content); ``` ```python Python SDK (Async) import asyncio +import os from hydra_db import AsyncHydraDB from hydra_db.helpers import build_string from openai import AsyncOpenAI -hydra = AsyncHydraDB(token="YOUR_HYDRA_DB_API_KEY") -openai_client = AsyncOpenAI(api_key="YOUR_OPENAI_API_KEY") +hydra = AsyncHydraDB(token=os.environ["HYDRA_DB_API_KEY"]) +openai_client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"]) question = "How does authentication work?" - async def main(): result = await hydra.query( database="your-database", + collection="your-collection", query=question, type="knowledge", query_by="hybrid", @@ -276,437 +147,230 @@ async def main(): print(completion.choices[0].message.content) - asyncio.run(main()) ``` ---- + + +Read `data.llm_prompt` from the JSON response. Use that string as `context` in the model call above. + + +```python Python +import os +import httpx + +response = httpx.post( + "https://api.hydradb.com/query", + headers={"Authorization": f"Bearer {os.environ['HYDRA_DB_API_KEY']}", "API-Version": "2"}, + json={"database": "acme_corp", "query": "What is our refund policy?"}, + timeout=60, +) +response.raise_for_status() +context = response.json()["data"]["llm_prompt"] +``` +```typescript TypeScript +const response = await fetch("https://api.hydradb.com/query", { + method: "POST", + headers: { + Authorization: `Bearer ${process.env.HYDRA_DB_API_KEY}`, + "API-Version": "2", + "Content-Type": "application/json", + }, + body: JSON.stringify({ database: "acme_corp", query: "What is our refund policy?" }), +}); +if (!response.ok) throw new Error(`Query failed with HTTP ${response.status}`); +const result = await response.json(); +const context = result.data.llm_prompt; +``` +```bash cURL +curl --fail-with-body 'https://api.hydradb.com/query' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{"database":"acme_corp","query":"What is our refund policy?"}' +``` + + + + +### Request Markdown only + +Set `response_format: "markdown"` to return the same context text directly as `data`, instead of inside `data.llm_prompt`. The SDK helpers above handle both formats. + +```json +{ + "database": "acme_corp", + "query": "What is our refund policy?", + "response_format": "markdown" +} +``` + +### Read citations and content labels + +The prompt numbers ranked results as `[1]`, `[2]`, and so on. Explicitly linked results use `[R1]`, `[R2]`, and so on. Ask your model to keep these labels when citing the context. + +A result can contain **Original content**, the source text, **Enriched content**, context HydraDB added, or both. Sections for related facts, dated facts, and sources appear when relevant. A query with no matches returns a no-results message rather than an empty prompt, including any filters that constrained the search. + +## Work with structured results + +Use `response_format: "structured"` to omit `llm_prompt` when your application formats the results itself. Individual passages, scores, and source details are available in both the default and structured responses. + + +```python Python SDK +result = hydra.query( + database="your-database", + collection="your-collection", + query="How does authentication work?", + response_format="structured", +) + +for chunk in result.data.chunks: + print(chunk.source_title, chunk.chunk_content) +``` +```typescript TypeScript SDK +const result = await hydra.query({ + database: "your-database", + collection: "your-collection", + query: "How does authentication work?", + responseFormat: "structured", +}); + +if (!result.data || typeof result.data === "string") { + throw new Error("Expected structured query results."); +} +for (const chunk of result.data.chunks ?? []) { + console.log(chunk.sourceTitle, chunk.chunkContent); +} +``` +```bash cURL +curl --fail-with-body 'https://api.hydradb.com/query' \ + -H "Authorization: Bearer $HYDRA_DB_API_KEY" \ + -H "API-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "database": "your-database", + "collection": "your-collection", + "query": "How does authentication work?", + "response_format": "structured" + }' +``` + -## 4. Combining Knowledge and Memories + -The simplest path is one `POST /query` with `type: "all"` - HydraDB queries both stores in parallel and returns one merged, ranked result set. +- `chunks` contains matching passages, scores, source IDs, and titles. Preserve the returned order. +- `graph_context.query_paths` contains relationships relevant to the query. See [Context Graphs](/essentials/v2/context-graphs). +- `graph_context.chunk_relations` groups relationships by `group_id`. Use `chunk_id_to_group_ids` to connect them to chunks. +- `additional_context` maps `extra_context_ids` to related chunks. + +See [Query](/api-reference/v2/endpoint/query) for the full response schema and examples. + + + + + +## Combine knowledge and memories + +Use one `POST /query` with `type: "all"`. HydraDB queries both stores in parallel and returns one merged, ranked result set. + +This example searches shared documents in `company_docs` and the user's memories in `user_123`. Use the collections you ingested into; both must exist. For restricted documents, also send the caller's identity as `acl`. Use the printed context in the [model call shown above](#send-context-to-your-model). ```python Python SDK +import os from hydra_db import HydraDB from hydra_db.helpers import build_string -from openai import OpenAI -hydra = HydraDB(token="YOUR_HYDRA_DB_API_KEY") -openai_client = OpenAI(api_key="YOUR_OPENAI_API_KEY") +hydra = HydraDB(token=os.environ["HYDRA_DB_API_KEY"]) question = "What is our refund policy?" result = hydra.query( database="acme_corp", - collection="user_123", + collections=["company_docs", "user_123"], query=question, type="all", query_by="hybrid", mode="thinking", ) -context = build_string(result) - -completion = openai_client.chat.completions.create( - model="gpt-4o", - messages=[ - {"role": "system", "content": "Answer using only the context provided."}, - {"role": "user", "content": f"Context:\n{context}\n\nQuestion: {question}"}, - ], -) - -print(completion.choices[0].message.content) +print(build_string(result)) ``` ```typescript TypeScript SDK import { HydraDBClient } from "@hydradb/sdk"; import { buildString } from "@hydradb/sdk/helpers"; -import OpenAI from "openai"; const hydra = new HydraDBClient({ token: process.env.HYDRA_DB_API_KEY }); -const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const question = "What is our refund policy?"; const result = await hydra.query({ database: "acme_corp", - collection: "user_123", + collections: ["company_docs", "user_123"], query: question, type: "all", queryBy: "hybrid", mode: "thinking", }); -const context = buildString(result); - -const completion = await openai.chat.completions.create({ - model: "gpt-4o", - messages: [ - { role: "system", content: "Answer using only the context provided." }, - { role: "user", content: `Context:\n${context}\n\nQuestion: ${question}` }, - ], -}); - -console.log(completion.choices[0].message.content); +console.log(buildString(result)); ``` ```python Python SDK (Async) import asyncio +import os from hydra_db import AsyncHydraDB from hydra_db.helpers import build_string -from openai import AsyncOpenAI -hydra = AsyncHydraDB(token="YOUR_HYDRA_DB_API_KEY") -openai_client = AsyncOpenAI(api_key="YOUR_OPENAI_API_KEY") +hydra = AsyncHydraDB(token=os.environ["HYDRA_DB_API_KEY"]) question = "What is our refund policy?" - async def main(): result = await hydra.query( database="acme_corp", - collection="user_123", + collections=["company_docs", "user_123"], query=question, type="all", query_by="hybrid", mode="thinking", ) - context = build_string(result) - - completion = await openai_client.chat.completions.create( - model="gpt-4o", - messages=[ - {"role": "system", "content": "Answer using only the context provided."}, - {"role": "user", "content": f"Context:\n{context}\n\nQuestion: {question}"}, - ], - ) - - print(completion.choices[0].message.content) - + print(build_string(result)) asyncio.run(main()) ``` -If you need to format knowledge and memories in separate labeled sections, call `POST /query` twice in parallel: - - -```python Python SDK -import asyncio -from hydra_db import AsyncHydraDB -from hydra_db.helpers import build_string - -hydra = AsyncHydraDB(token="YOUR_HYDRA_DB_API_KEY") - -async def main(): - knowledge_result, memory_result = await asyncio.gather( - hydra.query( - database="acme_corp", - query="refund policy", - type="knowledge", - query_by="hybrid", - mode="thinking", - ), - hydra.query( - database="acme_corp", - collection="user_123", - query="answer style preferences", - type="memory", - query_by="hybrid", - ), - ) - - prompt = ( - f"User preferences:\n{build_string(memory_result)}\n\n" - f"Relevant docs:\n{build_string(knowledge_result)}\n\n" - f"Question: {question}" - ) - return prompt - - -asyncio.run(main()) -``` -```typescript TypeScript SDK -import { HydraDBClient } from "@hydradb/sdk"; -import { buildString } from "@hydradb/sdk/helpers"; - -const hydra = new HydraDBClient({ token: process.env.HYDRA_DB_API_KEY }); - -const [knowledgeResult, memoryResult] = await Promise.all([ - hydra.query({ - database: "acme_corp", - query: "refund policy", - type: "knowledge", - queryBy: "hybrid", - mode: "thinking", - }), - hydra.query({ - database: "acme_corp", - collection: "user_123", - query: "answer style preferences", - type: "memory", - queryBy: "hybrid", - }), -]); - -const prompt = - `User preferences:\n${buildString(memoryResult)}\n\n` + - `Relevant docs:\n${buildString(knowledgeResult)}\n\n` + - `Question: ${question}`; -``` - - -If memory query fails or times out, fall back to the knowledge-only prompt. +If knowledge and memories need different search terms, make separate `POST /query` requests with `type: "knowledge"` and `type: "memory"`. --- -## 5. Practical guidance - -- **Preserve server order.** Don't re-sort chunks client-side. -- **Start small on chunks.** `max_results: 10` is a reasonable default. Drop to 5 if you hit token limits, raise to 20 if you rerank downstream. -- **Use graph context selectively.** It improves relational queries and bloats simple lookups. See [Context Graphs](/essentials/v2/context-graphs). -- **Always give the model a grounding instruction.** A system prompt like "answer only from the provided context" prevents the model from inventing answers when retrieval is thin. -- **Format consistently.** Whatever section delimiters you choose (`=== CONTEXT ===`, `Chunk N`, `Source:`), keep them stable across calls so the model learns the structure. -- **`type: "all"` is the simplest path when you need both knowledge and memories.** One call, one result set, one `build_string` call. + + ---- + -## 6. Common mistakes +## Common mistakes | Mistake | What goes wrong | Fix | |---|---|---| +| Reading `data.llm_prompt` with `response_format: "structured"` | The prompt field is omitted | Read the structured fields, or omit `response_format` to get both. | | Passing the raw result object to the LLM | Wastes tokens on IDs and metadata | Use `build_string` / `buildString`. | | Not including `collection` (formerly `sub_tenant_id`) when querying memories | Queries the default collection instead of the user's | Always pass the same `collection` used at ingestion. | | Including too many chunks | Token overflow or answer quality drops | Start at `max_results: 10`; reduce if needed. | | Re-sorting chunks client-side | Overrides HydraDB's ranking | Preserve the server-returned order. | | Missing a grounding instruction | The model invents answers when retrieval is thin | System prompt: answer only from the provided context. | -| Setting `graph_context: false` and expecting graph fields | Graph context will be omitted | `graph_context` is on by default; only set it to `false` when you explicitly don't want graph data. | -| Using `query_forceful_relations` with `mode: "fast"` | Flag is silently ignored | `query_forceful_relations` only takes effect when `mode: "thinking"`. | +| Setting `graph_context: false` and expecting graph fields | Graph context will be omitted (in fast mode; thinking always returns it) | `graph_context` is on by default; only set it to `false` when you explicitly don't want graph data. | +| Using `query_forceful_relations` with `mode: "fast"` | Flag is ignored | `query_forceful_relations` only takes effect when `mode: "thinking"` (or `"auto"` routed to thinking). | | Looking up `chunk_relations` without `chunk_id_to_group_ids` | The right relations don't attach to the right chunk | Use `chunk_id_to_group_ids[chunk_uuid]` to find the `group_id`s, then filter `chunk_relations` by them. | --- ## Related -- [Query](/essentials/v2/query) - request parameters and response shape -- [Memories](/essentials/v2/memories) - what `POST /query` with `type: "memory"` queries -- [Knowledge](/essentials/v2/knowledge) - what `POST /query` with `type: "knowledge"` queries -- [Context Graphs](/essentials/v2/context-graphs) - what `graph_context` fields contain - ---- - - - -If you are not using the SDK, use the helper below to call `POST /query`, format the response, and pass the result to your LLM. - - -```python Python -from __future__ import annotations -from typing import Any - -import httpx - - -def _format_path_chain(path: Any) -> str: - triplets = path.get("triplets") or [] - parts = [] - for t in triplets: - src = t.get("source", {}).get("name", "") - tgt = t.get("target", {}).get("name", "") - rel = t.get("relation", {}) - pred = rel.get("canonical_predicate", "") - line = f"[{src}] -> {pred} -> [{tgt}]" - ctx = rel.get("context") - if ctx: - line += f": {ctx}" - temporal = rel.get("temporal_details") - if temporal: - line += f" [Time: {temporal}]" - parts.append(line) - return "\n ↳ ".join(parts) - - -def build_context_string(result: dict) -> str: - lines: list[str] = [] - gc = result.get("graph_context") or {} - - query_paths = gc.get("query_paths") or [] - if query_paths: - lines.append("=== ENTITY PATHS ===") - for path in query_paths: - lines.append(_format_path_chain(path)) - lines.append("") - - chunks = result.get("chunks") or [] - additional_context = result.get("additional_context") or {} - chunk_id_to_group_ids = gc.get("chunk_id_to_group_ids") or {} - chunk_relations = gc.get("chunk_relations") or [] - - if chunks: - lines.append("=== CONTEXT ===") - for i, chunk in enumerate(chunks): - lines.append(f"Chunk {i + 1}") - source = chunk.get("source_title", "") - if source: - lines.append(f"Source: {source}") - lines.append(chunk.get("chunk_content", "")) - - chunk_uuid = chunk.get("chunk_uuid", "") - if chunk_uuid and chunk_id_to_group_ids and chunk_relations: - group_ids = chunk_id_to_group_ids.get(chunk_uuid, []) - relevant = [r for r in chunk_relations if r.get("group_id") in group_ids] - if relevant: - lines.append("Graph Relations:") - for rel in relevant: - for triplet in rel.get("triplets") or []: - src = triplet.get("source", {}).get("name", "") - tgt = triplet.get("target", {}).get("name", "") - pred = triplet.get("relation", {}).get("canonical_predicate", "") - ctx = triplet.get("relation", {}).get("context", "") - line = f" [{src}] -> {pred} -> [{tgt}]: {ctx}" - temporal = triplet.get("relation", {}).get("temporal_details") - if temporal: - line += f" [Time: {temporal}]" - lines.append(line) - - extra_ids = chunk.get("extra_context_ids") or [] - if extra_ids and additional_context: - extras = [additional_context[eid] for eid in extra_ids if eid in additional_context] - if extras: - lines.append("Extra Context:") - for extra in extras: - lines.append(f" Related Context ({extra.get('source_title', '')}): {extra.get('chunk_content', '')}") - - lines.append("---") - lines.append("") - - return "\n".join(lines) - - -# Call the API and format the result -response = httpx.post( - "https://api.hydradb.com/query", - headers={ - "Authorization": "Bearer YOUR_HYDRA_DB_API_KEY", - "API-Version": "2", - }, - json={ - "database": "your-database", - "query": "How does authentication work?", - "type": "knowledge", - "query_by": "hybrid", - "mode": "fast", - "graph_context": True, - }, -) - -response.raise_for_status() -envelope = response.json() -if not envelope.get("success"): - error = envelope.get("error") or {} - raise RuntimeError(error.get("message", "HydraDB search failed")) - -context = build_context_string(envelope["data"]) - -# Pass context to your LLM -print(context) -``` -```typescript TypeScript -function formatPathChain(path: any): string { - return (path.triplets || []) - .map((t: any) => { - let str = `[${t.source?.name}] -> ${t.relation?.canonical_predicate} -> [${t.target?.name}]`; - if (t.relation?.context) str += `: ${t.relation.context}`; - if (t.relation?.temporal_details) str += ` [Time: ${t.relation.temporal_details}]`; - return str; - }) - .join("\n ↳ "); -} - -function buildContextString(result: any): string { - const lines: string[] = []; - const gc = result.graph_context; - - if (gc?.query_paths?.length) { - lines.push("=== ENTITY PATHS ==="); - for (const path of gc.query_paths) lines.push(formatPathChain(path)); - lines.push(""); - } - - if (result.chunks?.length) { - lines.push("=== CONTEXT ==="); - for (let i = 0; i < result.chunks.length; i++) { - const chunk = result.chunks[i]; - lines.push(`Chunk ${i + 1}`); - lines.push(`Source: ${chunk.source_title}`); - lines.push(chunk.chunk_content); - - if (gc?.chunk_id_to_group_ids && gc.chunk_relations) { - const groupIds = gc.chunk_id_to_group_ids[chunk.chunk_uuid] || []; - const relations = gc.chunk_relations.filter( - (r: any) => r.group_id && groupIds.includes(r.group_id) - ); - if (relations.length) { - lines.push("Graph Relations:"); - for (const rel of relations) { - for (const t of rel.triplets) { - let line = ` [${t.source.name}] -> ${t.relation.canonical_predicate} -> [${t.target.name}]: ${t.relation.context}`; - if (t.relation.temporal_details) line += ` [Time: ${t.relation.temporal_details}]`; - lines.push(line); - } - } - } - } - - if (chunk.extra_context_ids?.length && result.additional_context) { - const extras = chunk.extra_context_ids - .map((id: string) => result.additional_context[id]) - .filter(Boolean); - if (extras.length) { - lines.push("Extra Context:"); - for (const extra of extras) { - lines.push(` Related Context (${extra.source_title}): ${extra.chunk_content}`); - } - } - } - - lines.push("---"); - lines.push(""); - } - } - return lines.join("\n"); -} - -// Call the API and format the result -const res = await fetch("https://api.hydradb.com/query", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer YOUR_HYDRA_DB_API_KEY", - "API-Version": "2", - }, - body: JSON.stringify({ - database: "your-database", - query: "How does authentication work?", - type: "knowledge", - query_by: "hybrid", - mode: "fast", - graph_context: true, - }), -}); - -const envelope = await res.json(); -if (!res.ok || !envelope.success) { - throw new Error(envelope.error?.message ?? "HydraDB search failed"); -} - -const context = buildContextString(envelope.data); - -// Pass context to your LLM -console.log(context); -``` - - - +- [Query](/essentials/v2/query): request parameters and response shape +- [Memories](/essentials/v2/memories): what `POST /query` with `type: "memory"` queries +- [Knowledge](/essentials/v2/knowledge): what `POST /query` with `type: "knowledge"` queries +- [Context Graphs](/essentials/v2/context-graphs): what `graph_context` fields contain diff --git a/essentials/v2/connectors.mdx b/essentials/v2/connectors.mdx index 6a661567..ad8ece0e 100644 --- a/essentials/v2/connectors.mdx +++ b/essentials/v2/connectors.mdx @@ -279,8 +279,8 @@ curl -X POST 'https://api.hydradb.com/connectors/{id}/configure' \ | `sync_interval_seconds` | create | How often scheduled syncs run. Omit it for the provider default, one hour for most providers. | | `lookback_days` | configure | How much history the first sync fetches (default `30`). After that, each sync fetches only what is new. Above 30, some providers fetch the older history in the background and the response reports `backfill: true`. | | `resource_id`, `resource_type`, `name` | configure | The `id`, `resource_type`, and `name` of an entry from discover. Send all three every time you list a resource. | -| `metadata` | configure | Fields merged into the attributes of every object from this resource. Declare the ones you filter on in your database's [metadata schema](/essentials/v2/metadata). | -| `additional_metadata` | configure | Free-form fields merged into the custom attributes of every object from this resource. No schema needed. | +| `metadata` | configure | Fields merged into the database metadata of every object from this resource. Declare the ones you filter on in your database's [metadata schema](/essentials/v2/metadata). | +| `additional_metadata` | configure | Free-form fields merged into the additional metadata of every object from this resource. No schema needed. | | `acl` | configure | Who can read this resource's objects. Omitted means unrestricted. See [permissions](#6-permissions-on-synced-content). | @@ -481,15 +481,15 @@ Before you write one: Every object a connector syncs lands in HydraDB with two metadata layers, the same two that [Metadata](/essentials/v2/metadata) describes for everything else. -**Attributes (`metadata`)** are the schema-declared layer: fields are declared once in your database's metadata schema and filtered with top-level `metadata_filters` keys. HydraDB always writes `connector_id` and `provider` here for every synced object. Your own `metadata` from configure is merged first, so `connector_id` and `provider` win on conflict. +`metadata` contains fields declared in your database's metadata schema. Filter them with top-level `metadata_filters` keys. HydraDB always writes `connector_id` and `provider` here for every synced object. Your own `metadata` from configure is merged first, so `connector_id` and `provider` win on conflict. -**Custom attributes (`additional_metadata`)** are the free-form layer and need no schema. HydraDB always writes `connector_id` and `resource_id` here, plus provider-native fields such as a Slack message timestamp, a GitHub issue number, or a Linear identifier. Your own `additional_metadata` from configure is merged first, so provider-generated fields win on conflict. +`additional_metadata` contains free-form fields and needs no schema. HydraDB always writes `connector_id` and `resource_id` here, plus provider-native fields such as a Slack message timestamp, a GitHub issue number, or a Linear identifier. Your own `additional_metadata` from configure is merged first, so provider-generated fields win on conflict. This is the layer to filter on to scope a query to one connector, channel, repository, or table: -```json Querying with a custom attribute filter +```json Querying with a metadata filter { "database": "acme_corp", "query": "deployment checklist", @@ -570,7 +570,7 @@ All of them can sync into the same collection, so one query searches every accou - [Connectors API reference](/api-reference/v2/endpoint/connectors-overview): every endpoint, and which one changes what - [App Sources](/essentials/v2/app-sources): the ingestion model connector objects use, and how to bring your own connector -- [Metadata](/essentials/v2/metadata): attributes and custom attributes in depth +- [Metadata](/essentials/v2/metadata): schema-backed and free-form metadata - [Multi-tenancy](/essentials/v2/multi-tenant): routing resources to databases and collections - [Query](/essentials/v2/query): querying connector-synced data - [Access Control](/essentials/v2/access-control): restricting who can retrieve synced data diff --git a/style.css b/style.css index ab1eede7..a1fa5413 100644 --- a/style.css +++ b/style.css @@ -23,6 +23,10 @@ display: none; } +.mdx-content span[id]:empty { + scroll-margin-top: 9.5rem; +} + .mdx-content:has(.api-before-related-resources) > div:has(> .api-section) { order: 1; }