> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modulex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Qdrant

> Connect Qdrant as a bring-your-own vector store in ModuleX: create a Qdrant connection credential, configure a Knowledge node to query your collections, and understand BYOK retrieval with no ModuleX credit cost.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

Qdrant is a **bring-your-own (BYOK) external knowledge provider** in ModuleX. You connect your own Qdrant instance — Qdrant Cloud or self-hosted — as a [credential](/concepts/credentials-oauth), then point a workflow [Knowledge node](/workflow-builder/nodes/knowledge) at one of your collections to run vector similarity search at run time. Because retrieval runs against your own Qdrant, it is **uncosted** in ModuleX: it consumes **no [credits](/concepts/credits-billing)** and never hits the billing gate.

This page covers connecting a Qdrant credential, the connection fields and Knowledge-node configuration, how BYOK retrieval works under the hood, the catalog actions Qdrant exposes, and the errors you can hit. For the cross-provider picture see [Knowledge providers](/integrations/knowledge-providers/overview); for the managed (credit-metered) alternative see [modulexdb](/integrations/knowledge-providers/modulexdb); for the retrieval concept see [Knowledge & RAG](/concepts/knowledge-rag).

<Note>
  Qdrant is the integration named `qdrant` on the wire, with `integration_type` `knowledge_provider`. It ships **one auth schema** of type `custom` ("Qdrant Connection"). Unlike [managed knowledge](/platform/knowledge/managed) (the `modulexdb` provider), ModuleX does **not** ingest, chunk, embed, or store documents for you in Qdrant — you own the collections, the points, and the embeddings. ModuleX only **queries** what is already in your Qdrant.
</Note>

## How Qdrant fits in ModuleX

ModuleX treats a vector store one of two ways:

<CardGroup cols={2}>
  <Card title="Managed (modulexdb)" icon="server">
    ModuleX hosts the vectors, ingests and embeds your documents, and bills retrieval and ingest in [credits](/concepts/credits-billing). See [Managed knowledge](/platform/knowledge/managed).
  </Card>

  <Card title="BYOK (Qdrant, external)" icon="plug">
    You host the vectors in your own Qdrant. ModuleX queries them through an adapter using a [credential](/concepts/credentials-oauth) you create. Retrieval is **uncosted** — no credits, no billing gate.
  </Card>
</CardGroup>

Qdrant requires a **query vector** — it does not embed text for you. So a Knowledge node pointed at Qdrant first embeds your query with the embedding model you configure (for example an [OpenAI](/integrations/llm-providers/openai) embedding model), then sends that vector to Qdrant's search API. This means a Qdrant Knowledge node needs an `embedding_config`; ModuleX raises a configuration error without one.

<Warning>
  The embedding model used at query time **must match** the model that produced the vectors already stored in your Qdrant collection — same provider, same model, same dimension. If they differ, Qdrant returns either a dimension-mismatch error or low-quality results. ModuleX does not validate this for you because it never wrote those vectors.
</Warning>

<MediaEmbed id="MX-MEDIA-4130" type="image" caption={"Diagram of the BYOK Qdrant retrieval path inside a workflow run."} />

## Before you start

<CardGroup cols={2}>
  <Card title="A reachable Qdrant instance" icon="database">
    A Qdrant Cloud cluster or a self-hosted Qdrant that ModuleX can reach over HTTP(S), with at least one collection that already contains embedded points.
  </Card>

  <Card title="Owner or admin role" icon="user-shield">
    Creating and managing credentials, and browsing the integration catalog, require the **owner** or **admin** role in your [organization](/concepts/organizations-roles). The retired `member` role cannot do this. See [Roles & permissions](/security/roles-permissions).
  </Card>

  <Card title="Your auth headers" icon="lock">
    Every API call sends `Authorization: Bearer mx_live_…` and `X-Organization-ID`. See [Authentication](/api-reference/authentication).
  </Card>

  <Card title="A matching embedding model" icon="vector-square">
    Know which embedding model and dimension produced the vectors in your collection, so you can configure the same model for query embedding.
  </Card>
</CardGroup>

## Connect Qdrant

You connect Qdrant by creating a `custom` [credential](/concepts/credentials-oauth) for the `qdrant` integration. The credential stores your Qdrant URL and (optionally) an API key. ModuleX encrypts the credential at rest and never returns the secret material in clear text.

<Steps>
  <Step title="Gather your Qdrant connection details">
    Note your Qdrant **URL** (including the port — Qdrant's REST API listens on `6333` by default) and, for Qdrant Cloud, your **API key**. A local instance typically needs no API key.
  </Step>

  <Step title="(Optional) validate the connection before saving">
    Call `POST /credentials/test-temporary` to check the connection **before** persisting it. ModuleX runs Qdrant's configured test — a `GET {url}/collections` with your `api-key` header — and reports `is_valid`. See [Validate a connection before saving](#validate-a-connection-before-saving).
  </Step>

  <Step title="Create the credential">
    Call `POST /credentials` with `integration_name: "qdrant"`, `auth_type: "custom"`, and an `auth_data` object holding `url` and optional `api_key`. Set `make_default: true` to make it the default Qdrant credential for the organization.
  </Step>

  <Step title="Reference it from a Knowledge node">
    Use the returned `credential_id` (plus a `collection_name` and an `embedding_config`) in a workflow [Knowledge node](/workflow-builder/nodes/knowledge). See [Configure a Knowledge node for Qdrant](#configure-a-knowledge-node-for-qdrant).
  </Step>
</Steps>

<MediaEmbed id="MX-MEDIA-4131" type="screenshot" caption={"The ModuleX app credential dialog for connecting Qdrant."} />

### Connection fields

The Qdrant auth schema is a single `custom` schema named **"Qdrant Connection"** with two fields. Send them inside `auth_data`.

<ParamField body="url" type="string" required>
  The base URL of your Qdrant instance, including the port — for example `https://xyz-abc.aws.cloud.qdrant.io:6333`. A trailing slash is stripped. When omitted at run time the adapter falls back to `http://localhost:6333`, but the field is **required** when you create a credential. Not sensitive.
</ParamField>

<ParamField body="api_key" type="string">
  Your Qdrant Cloud API key. Optional — leave it unset for local or unauthenticated instances. Sent to Qdrant as the `api-key` request header. Sensitive; stored encrypted and masked in responses.
</ParamField>

### Create the credential

`POST /credentials` returns `201`. For Qdrant you must send `auth_type: "custom"` because the connection is not an `api_key`-only schema (the URL is part of the connection). See the full credential model in [Credentials & OAuth2](/concepts/credentials-oauth).

<ParamField body="integration_name" type="string" required>
  Must be `qdrant`.
</ParamField>

<ParamField body="auth_type" type="string" required>
  `custom`.
</ParamField>

<ParamField body="auth_data" type="object" required>
  The connection material — `{"url": "...", "api_key": "..."}`. `api_key` may be omitted for instances that do not require one.
</ParamField>

<ParamField body="display_name" type="string">
  A human-readable label. Defaults to the integration's display name (`Qdrant`) when omitted.
</ParamField>

<ParamField body="make_default" type="boolean" default="false">
  Set this credential as the default Qdrant credential for the organization. Only one credential per integration can be the default.
</ParamField>

**Response** — `201 Created` (secret masked):

<ResponseField name="credential_id" type="string">
  The credential's UUID. Pass this as `credential_id` in a Knowledge node to use this Qdrant connection.
</ResponseField>

<ResponseField name="integration_name" type="string">
  `qdrant`.
</ResponseField>

<ResponseField name="integration_type" type="string | null">
  `knowledge_provider`.
</ResponseField>

<ResponseField name="auth_type" type="string">
  `custom`.
</ResponseField>

<ResponseField name="is_default" type="boolean">
  Whether this is the organization's default Qdrant credential.
</ResponseField>

<ResponseField name="last_used_at" type="string | null">
  ISO-8601 timestamp of the last resolution, or `null` if never used.
</ResponseField>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/credentials \
    -H "Authorization: Bearer mx_live_xxx" \
    -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \
    -H "Content-Type: application/json" \
    -d '{
      "integration_name": "qdrant",
      "auth_type": "custom",
      "display_name": "Production Qdrant",
      "make_default": true,
      "auth_data": {
        "url": "https://xyz-abc.aws.cloud.qdrant.io:6333",
        "api_key": "qdrant-api-key-abc123"
      }
    }'
  ```

  ```python Python theme={null}
  from modulex import Modulex

  async with Modulex(
      api_key="mx_live_xxx",
      organization_id="11111111-1111-1111-1111-111111111111",
  ) as client:
      credential = await client.credentials.create(
          "qdrant",
          auth_type="custom",
          display_name="Production Qdrant",
          make_default=True,
          auth_data={
              "url": "https://xyz-abc.aws.cloud.qdrant.io:6333",
              "api_key": "qdrant-api-key-abc123",
          },
      )
      print(credential.credential_id)
  ```

  ```javascript JavaScript theme={null}
  import { Modulex } from "modulex-js";

  const client = new Modulex({
    apiKey: "mx_live_xxx",
    organizationId: "11111111-1111-1111-1111-111111111111",
  });

  const credential = await client.credentials.create({
    integrationName: "qdrant",
    authType: "custom",
    displayName: "Production Qdrant",
    makeDefault: true,
    authData: {
      url: "https://xyz-abc.aws.cloud.qdrant.io:6333",
      api_key: "qdrant-api-key-abc123",
    },
  });
  console.log(credential.credential_id);
  ```
</CodeGroup>

### Validate a connection before saving

`POST /credentials/test-temporary` validates the connection **before** you persist it. ModuleX runs Qdrant's configured test endpoint — `GET {url}/collections`, sending the `api-key` header — and treats HTTP `200` with a `result` field present as success. The test is free (`cost_level: "free"`).

<ParamField body="integration_name" type="string" required>
  `qdrant`.
</ParamField>

<ParamField body="auth_type" type="string" required>
  `custom`.
</ParamField>

<ParamField body="auth_data" type="object" required>
  `{"url": "...", "api_key": "..."}`.
</ParamField>

The response reports `is_valid`, a human-readable `message`, `tested_at`, the `test_method`, and the `status_code` returned by Qdrant.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/credentials/test-temporary \
    -H "Authorization: Bearer mx_live_xxx" \
    -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \
    -H "Content-Type: application/json" \
    -d '{
      "integration_name": "qdrant",
      "auth_type": "custom",
      "auth_data": {
        "url": "https://xyz-abc.aws.cloud.qdrant.io:6333",
        "api_key": "qdrant-api-key-abc123"
      }
    }'
  ```

  ```python Python theme={null}
  result = await client.credentials.test_temporary(
      "qdrant",
      "custom",
      {
          "url": "https://xyz-abc.aws.cloud.qdrant.io:6333",
          "api_key": "qdrant-api-key-abc123",
      },
  )
  print(result.is_valid, result.message)
  ```

  ```javascript JavaScript theme={null}
  const result = await client.credentials.testTemporary({
    integrationName: "qdrant",
    authType: "custom",
    authData: {
      url: "https://xyz-abc.aws.cloud.qdrant.io:6333",
      api_key: "qdrant-api-key-abc123",
    },
  });
  console.log(result.isValid, result.message);
  ```
</CodeGroup>

## Configure a Knowledge node for Qdrant

A workflow [Knowledge node](/workflow-builder/nodes/knowledge) is the surface that retrieves from Qdrant during a [run](/concepts/workflows-and-runs). For an external provider you set `provider_type` to `qdrant`, point it at a `collection_name`, and supply an `embedding_config` so ModuleX can embed the query before searching. The full node configuration is below; fields marked **(external)** apply to Qdrant and the other [external providers](/integrations/knowledge-providers/overview), and fields marked **(native only)** apply only to [modulexdb](/integrations/knowledge-providers/modulexdb).

<ParamField body="credential_id" type="string" required>
  The UUID of the Qdrant [credential](/concepts/credentials-oauth) to use.
</ParamField>

<ParamField body="provider_type" type="string" default="modulexdb">
  One of `modulexdb`, `qdrant`, `pinecone`, `weaviate`, `mongodb_atlas`. Set it to `qdrant`.
</ParamField>

<ParamField body="query" type="string" required>
  The search query. Supports `{{nodeId.path}}` references so the query can come from an earlier node's output. See [Variables & references](/workflow-builder/variables-and-references).
</ParamField>

<ParamField body="query_from_input" type="boolean" default="false">
  When `true`, use the workflow input as the query instead of the `query` field.
</ParamField>

<ParamField body="collection_name" type="string" required>
  **(external)** The Qdrant collection to search. Required for Qdrant — the node raises a configuration error without it.
</ParamField>

<ParamField body="namespace" type="string | null">
  **(external)** Namespace, used by [Pinecone](/integrations/knowledge-providers/pinecone)-style providers. Qdrant does not use namespaces; leave it unset.
</ParamField>

<ParamField body="embedding_config" type="object">
  **(external)** Required for Qdrant. The model ModuleX uses to embed the query into a vector before searching Qdrant. See [The `embedding_config` object](#the-embedding-config-object).
</ParamField>

<ParamField body="top_k" type="integer" default="5">
  Number of results to retrieve. Range `1`–`50`. Passed to Qdrant as the search `limit`.
</ParamField>

<ParamField body="min_score" type="number" default="0.3">
  Minimum similarity-score floor, `0.0`–`1.0`. When greater than `0`, sent to Qdrant as `score_threshold`; Qdrant drops points below it.
</ParamField>

<ParamField body="max_tokens" type="integer" default="2000">
  Maximum tokens in the formatted context string (used when `output_format` is `context` or `both`). Range `100`–`10000`.
</ParamField>

<ParamField body="filters" type="object | null">
  **(external)** A provider-specific filter, passed straight to Qdrant as its `filter` clause. Use Qdrant's filter syntax (for example `must`/`should`/`must_not` conditions on payload fields).
</ParamField>

<ParamField body="document_ids" type="string[] | null">
  **(native only)** Restrict retrieval to specific document IDs. Applies to modulexdb knowledge bases, not Qdrant.
</ParamField>

<ParamField body="output_format" type="string" default="context">
  How results are written into run state — `chunks` (individual matches with metadata), `context` (one RAG-ready context string), or `both`.
</ParamField>

<ParamField body="include_metadata" type="boolean" default="true">
  Include each match's payload/metadata in the results. Maps to Qdrant's `with_payload`.
</ParamField>

<ParamField body="include_source" type="boolean" default="true">
  Include source-document info in the formatted context string.
</ParamField>

### The `embedding_config` object

Qdrant stores vectors but does not embed text, so the Knowledge node embeds your query first. `embedding_config` selects the embedding model and is **required** for Qdrant.

<ParamField body="integration_name" type="string" required>
  The embedding provider integration, for example `openai`. See [LLM providers](/integrations/llm-providers/overview).
</ParamField>

<ParamField body="provider_id" type="string" required>
  The provider routing slug, for example `openai`.
</ParamField>

<ParamField body="model_id" type="string" required>
  The embedding model id, for example `text-embedding-3-small`. Must match the model that produced the vectors in your collection.
</ParamField>

<ParamField body="credential_id" type="string | null">
  The credential for the embedding provider. When `null`, ModuleX uses the organization's default credential for `integration_name`.
</ParamField>

```json Knowledge node config (Qdrant) theme={null}
{
  "credential_id": "c0ffee00-1111-2222-3333-444455556666",
  "provider_type": "qdrant",
  "collection_name": "product_docs",
  "query": "{{trigger.question}}",
  "top_k": 5,
  "min_score": 0.3,
  "output_format": "context",
  "embedding_config": {
    "integration_name": "openai",
    "provider_id": "openai",
    "model_id": "text-embedding-3-small",
    "credential_id": null
  }
}
```

<Note>
  The Knowledge node is configured on the canvas in the [workflow builder](/workflow-builder/nodes/knowledge), or programmatically as part of a workflow's node graph. There is no standalone REST endpoint that queries an external provider directly — Qdrant retrieval happens **inside a workflow run**. The native [knowledge-bases search API](/platform/knowledge/managed) (`POST /knowledge-bases/{id}/search`) targets modulexdb, not Qdrant.
</Note>

## How BYOK retrieval works

When a Knowledge node with `provider_type: "qdrant"` executes during a run, ModuleX:

<Steps>
  <Step title="Resolves and decrypts the credential">
    Loads your Qdrant credential for the current organization and decrypts the `url` and `api_key`. A missing or wrong-org credential fails the node with a credential error. See [Data security & encryption](/security/data-encryption).
  </Step>

  <Step title="Embeds the query">
    Because Qdrant requires a query vector, ModuleX embeds the resolved `query` using your `embedding_config`. Without `embedding_config`, the node raises a configuration error.
  </Step>

  <Step title="Searches your Qdrant collection">
    The Qdrant adapter calls `POST {url}/collections/{collection_name}/points/search` with the query vector, `limit` (`top_k`), `with_payload` (`include_metadata`), `with_vector`, an optional `score_threshold` (`min_score`), and any `filters`. It sends the `api-key` header when an API key is set.
  </Step>

  <Step title="Normalizes and formats results">
    Each Qdrant point becomes a standard match (`id`, `score`, extracted text content, payload metadata). Text content is pulled from common payload fields (`content`, `text`, `chunk_text`, `page_content`, `data`, `body`, `summary`, `document`). The node then writes them into run state as chunks, a context string, or both per `output_format`. See [Workflow engine & nodes](/concepts/workflow-engine).
  </Step>
</Steps>

<Warning>
  Text-content extraction depends on your payload using one of the recognized field names. If your Qdrant points store their text under a different key, the returned `content` is empty (the metadata still comes through when `include_metadata` is on). Store retrievable text under a field such as `content` or `text`.
</Warning>

## BYOK billing

Qdrant is **bring-your-own**. Retrieval runs against your Qdrant instance and is **not** metered in ModuleX [credits](/concepts/credits-billing). Only **managed** knowledge — `modulexdb` knowledge bases — is credit-billed for retrieval and ingest.

<CardGroup cols={2}>
  <Card title="No ModuleX credit charge" icon="circle-check">
    Querying Qdrant draws down no plan allowance and no [wallet](/billing/wallet) balance. There is no per-retrieval credit cost.
  </Card>

  <Card title="No billing-gate denials" icon="shield-check">
    Because BYOK retrieval is uncosted, a Qdrant Knowledge node never produces the `402`/`403`/`429` `DenialEnvelope` that managed knowledge can. See [Usage gating & limits](/billing/usage-gating).
  </Card>

  <Card title="Embedding may still cost" icon="vector-square">
    The query-embedding step uses your `embedding_config`. If that points at a [BYOK model](/integrations/llm-providers/overview) (for example OpenAI), the embedding is billed by that provider. If it points at a managed model, the embedding is credit-metered.
  </Card>

  <Card title="Hosted by you" icon="building-columns">
    Storage, indexing, and query throughput for your Qdrant are governed by your Qdrant Cloud plan or self-hosted capacity, not by ModuleX.
  </Card>
</CardGroup>

## Browse Qdrant in the catalog

The [integration catalog](/integrations/catalog) is the read-only discovery surface. Use it to fetch Qdrant's metadata, actions, and auth schema. These catalog endpoints authenticate with a **Clerk JWT** (the app token) rather than an `mx_live_*` API key, and still require `X-Organization-ID` and the owner/admin role.

<ParamField path="provider_name" type="string" required>
  `qdrant`.
</ParamField>

**Response** — `200 OK`, an `IntegrationDetail`:

<ResponseField name="name" type="string">
  `qdrant`.
</ResponseField>

<ResponseField name="display_name" type="string">
  `Qdrant`.
</ResponseField>

<ResponseField name="integration_type" type="string">
  `knowledge_provider`.
</ResponseField>

<ResponseField name="categories" type="string[]">
  `["Vector Database", "semantic-search"]`.
</ResponseField>

<ResponseField name="actions" type="object[]">
  The catalog actions Qdrant declares — `query`, `list_collections`, `get_collection_info`. See [Catalog actions](#catalog-actions).
</ResponseField>

<ResponseField name="auth_schemas" type="object[]">
  The single `custom` "Qdrant Connection" schema, with its `fields` (`url`, `api_key`) and a `test_endpoint`.
</ResponseField>

<ResponseField name="docs_url" type="string | null">
  `null` for Qdrant (the package/JSON manifest carries `app_url` `https://qdrant.tech` but no `docs_url`).
</ResponseField>

<ResponseField name="metadata" type="object | null">
  The integration's `extra_metadata`. Note `provider_type` (`external`) is present in the source manifest but is **dropped on sync** and is not returned by the catalog API.
</ResponseField>

<Note>
  Knowledge-provider auth schemas use a `fields` array (not the `setup_environment_variables` array used by LLM/tool manifests). The catalog returns `auth_schemas` verbatim, so when you read a knowledge-provider detail, look for `fields`.
</Note>

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/integrations/knowledge-providers/qdrant \
    -H "Authorization: Bearer <clerk_jwt_token>" \
    -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111"
  ```

  ```bash cURL — list all knowledge providers theme={null}
  curl https://api.modulex.dev/integrations/knowledge-providers \
    -H "Authorization: Bearer <clerk_jwt_token>" \
    -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111"
  ```
</CodeGroup>

<Expandable title="Example detail response (truncated)">
  ```json theme={null}
  {
    "name": "qdrant",
    "display_name": "Qdrant",
    "description": "High-performance vector database with advanced filtering capabilities for semantic search",
    "logo": "logos:qdrant-icon",
    "app_url": "https://qdrant.tech",
    "docs_url": null,
    "categories": ["Vector Database", "semantic-search"],
    "integration_type": "knowledge_provider",
    "version": "1.0.0",
    "auth_schemas": [
      {
        "auth_type": "custom",
        "display_name": "Qdrant Connection",
        "description": "Connect to your Qdrant instance using URL and optional API key",
        "fields": [
          {
            "name": "url",
            "display_name": "Qdrant URL",
            "type": "string",
            "required": true,
            "sensitive": false,
            "sample_format": "https://xyz-abc.aws.cloud.qdrant.io:6333"
          },
          {
            "name": "api_key",
            "display_name": "API Key",
            "type": "string",
            "required": false,
            "sensitive": true,
            "sample_format": "qdrant-api-key-..."
          }
        ],
        "test_endpoint": {
          "url": "{url}/collections",
          "method": "GET",
          "headers": { "api-key": "{api_key}", "Content-Type": "application/json" },
          "success_indicators": { "status_codes": [200], "response_fields": ["result"] },
          "cost_level": "free"
        }
      }
    ],
    "actions": [
      { "name": "query", "description": "Perform vector similarity search on your Qdrant collection" },
      { "name": "list_collections", "description": "List all collections in the Qdrant instance" },
      { "name": "get_collection_info", "description": "Get information about a specific collection" }
    ]
  }
  ```
</Expandable>

### Catalog actions

Qdrant declares three actions in its manifest. These describe the operations the provider exposes; in practice they are driven by the [Knowledge node](#configure-a-knowledge-node-for-qdrant) (`query`) and used for connection discovery (`list_collections`, `get_collection_info`).

<Expandable title="query — vector similarity search">
  Performs vector similarity search on a Qdrant collection. Maps to `POST {url}/collections/{collection_name}/points/search`.

  <ParamField body="collection_name" type="string" required>
    Name of the Qdrant collection to search.
  </ParamField>

  <ParamField body="query_vector" type="array" required>
    The query embedding vector. Qdrant does not embed text — supply a vector. In a Knowledge node, ModuleX computes this from your `embedding_config`.
  </ParamField>

  <ParamField body="limit" type="integer" default="5">
    Maximum number of results to return.
  </ParamField>

  <ParamField body="score_threshold" type="number" default="0">
    Minimum similarity score (`0`–`1`). Applied only when greater than `0`.
  </ParamField>

  <ParamField body="filter" type="object">
    Optional Qdrant filter conditions for the search.
  </ParamField>

  <ParamField body="with_payload" type="boolean" default="true">
    Include each point's payload in the results.
  </ParamField>

  <ParamField body="with_vectors" type="boolean" default="false">
    Include the stored vectors in the results.
  </ParamField>

  **Result** — an array of points, each:

  <ResponseField name="id" type="string">
    The point id.
  </ResponseField>

  <ResponseField name="score" type="number">
    The similarity score.
  </ResponseField>

  <ResponseField name="payload" type="object">
    The point payload (when `with_payload` is true).
  </ResponseField>

  <ResponseField name="vector" type="array">
    The stored vector (when `with_vectors` is true).
  </ResponseField>
</Expandable>

<Expandable title="list_collections — list collections">
  Lists all collections in the Qdrant instance. Takes no parameters. Maps to `GET {url}/collections` (with a per-collection info lookup for counts).

  **Result** — an array, each:

  <ResponseField name="name" type="string">
    The collection name.
  </ResponseField>

  <ResponseField name="vectors_count" type="integer">
    Number of vectors (points) in the collection.
  </ResponseField>

  <ResponseField name="points_count" type="integer">
    Number of points in the collection.
  </ResponseField>
</Expandable>

<Expandable title="get_collection_info — collection details">
  Returns details about one collection. Maps to `GET {url}/collections/{collection_name}`.

  <ParamField body="collection_name" type="string" required>
    Name of the collection.
  </ParamField>

  **Result**:

  <ResponseField name="name" type="string">
    The collection name.
  </ResponseField>

  <ResponseField name="vectors_count" type="integer">
    Number of vectors in the collection.
  </ResponseField>

  <ResponseField name="points_count" type="integer">
    Number of points in the collection.
  </ResponseField>

  <ResponseField name="config" type="object">
    The collection configuration, including its vector parameters (size/dimension and distance metric).
  </ResponseField>
</Expandable>

## Errors and edge cases

<ResponseField name="Missing collection_name" type="configuration error">
  A Qdrant Knowledge node without `collection_name` fails with a configuration error before any call to Qdrant.
</ResponseField>

<ResponseField name="Missing embedding_config" type="configuration error">
  Qdrant requires a query vector. Without `embedding_config`, the node fails with an error asking you to configure `integration_name`, `provider_id`, `model_id`, and optionally `credential_id`.
</ResponseField>

<ResponseField name="Credential not found" type="run error">
  If the `credential_id` does not resolve in the current organization, the node fails with a credential-not-found error. Confirm the credential belongs to the org set in `X-Organization-ID`.
</ResponseField>

<ResponseField name="Invalid API key (401)" type="authentication error">
  Qdrant returning `401` surfaces as an authentication error ("Invalid API key"). Re-check the `api_key` in your credential.
</ResponseField>

<ResponseField name="Collection not found (404)" type="query error">
  Qdrant returning `404` for the search surfaces as a query error (`Collection not found: {name}`). Verify the collection exists on that instance.
</ResponseField>

<ResponseField name="Connection failure" type="connection error">
  If ModuleX cannot reach the Qdrant URL, the node fails with a connection error. Confirm the URL, port (`6333` by default), and that the instance is reachable from ModuleX.
</ResponseField>

<ResponseField name="Dimension mismatch" type="upstream error">
  If the query vector's dimension does not match the collection's vector size, Qdrant rejects the search. Use an embedding model whose dimension matches the collection.
</ResponseField>

<Note>
  These errors arise inside a [workflow run](/concepts/workflows-and-runs), so they appear as node/run errors in the run stream rather than as a top-level HTTP error envelope. For the error-envelope shapes on the REST surface, see [Errors & status codes](/api-reference/errors).
</Note>

## Managing the credential

Day-to-day operations use the standard [credentials API](/concepts/credentials-oauth#the-credentials-api). For Qdrant specifically:

* **Rotate the URL or key.** Secrets are immutable through `PUT /credentials/{id}` (that route updates only `display_name` and metadata). To change the URL or API key, create a new Qdrant credential and delete the old one.
* **Set the default.** `POST /credentials/{id}/set-default` makes one Qdrant credential the organization default, used when a node resolves to the default.
* **Test a saved connection.** `POST /credentials/{id}/test` re-runs Qdrant's test endpoint against the stored URL and key.
* **Delete.** `DELETE /credentials/{id}` removes it and returns `204`. Knowledge nodes pointing at it will fail credential resolution until another valid Qdrant credential exists.

See [Managing credentials](/integrations/managing-credentials) for the app workflow and [Credentials & OAuth2](/concepts/credentials-oauth) for the complete API.

## Where to go next

<CardGroup cols={2}>
  <Card title="Knowledge providers" icon="layer-group" href="/integrations/knowledge-providers/overview">
    Compare Qdrant with modulexdb and the other external vector stores.
  </Card>

  <Card title="modulexdb (managed)" icon="server" href="/integrations/knowledge-providers/modulexdb">
    The credit-metered, ModuleX-hosted alternative that ingests and embeds for you.
  </Card>

  <Card title="Knowledge node" icon="diagram-project" href="/workflow-builder/nodes/knowledge">
    Configure retrieval inside a workflow, including external providers like Qdrant.
  </Card>

  <Card title="Knowledge & RAG" icon="book-open" href="/concepts/knowledge-rag">
    How ModuleX retrieves company knowledge — managed and BYOK.
  </Card>
</CardGroup>
