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

# Manual Integration

> Query your users' data using natural language

While you can use the Hyperspell API directly to query memories, the most common way to query memories is using the Hyperspell SDKs, which are available for Python and TypeScript.

<Note>
  If you need an SDK for another language, please let us know and we'll create it for you.
</Note>

## Installation

<CodeGroup>
  ```bash Python theme={null}
  pip install hyperspell
  # Or using Poetry
  poetry add hyperspell
  # Or using uv
  uv add hyperspell
  ```

  ```bash TypeScript theme={null}
  npm install @hyperspell/hyperspell
  # Or using yarn
  yarn add @hyperspell/hyperspell
  ```
</CodeGroup>

To initialize the client, you need to generate an API Key in your [dashboard](https://app.hyperspell.com/api-keys).

<CodeGroup>
  ```python Python theme={null}
  from hyperspell import Hyperspell

  client = Hyperspell(api_key="API_KEY", user_id="YOUR_USER_ID")
  ```

  ```typescript TypeScript theme={null}
  import Hyperspell from 'hyperspell';

  const client = new Hyperspell({ apiKey: 'API_KEY', userID: 'YOUR_USER_ID' });
  ```
</CodeGroup>

You can also set the `HYPERSPELL_API_KEY` environment variable and omit the `api_key` parameter when initializing the client.

<Note>
  **EU data residency:** if your account is in the EU region, point the client at the EU API by passing `base_url="https://api.eu.hyperspell.com"` (Python) / `baseURL: 'https://api.eu.hyperspell.com'` (TypeScript). See [Regions & Data Residency](/concepts/data-residency).
</Note>

<Note>
  #### What is `YOUR_USER_ID`?

  Hyperspell is a multi-tenant platform, and you can have multiple users in your app. You don't have to explicitly create users in Hyperspell — you simply pass the user id from your own database or authentication provider to Hyperspell. Initializing the client with a user id ensures that you only query from that specific user.

  If you don't have an authentication system yet or are just testing things out you can omit this paramter too. If you've already connected your personal accounts in the [Sandbox](https://app.hyperspell.com/sandbox), you can use the user id `sandbox:youremail` to query your own data.
</Note>

## Usage

Before you can query data, you need to add a memory. In this example, we'll add a a simple poem. We'll also tag this poem by adding it to the collection `poems` — that lets us query for all poems later.

<CodeGroup>
  ```python Python theme={null}
  memory_status = client.memories.add(
      text="'Twas brillig, and the slithy toves did gyre and gimble in the wabe...",
      metadata={"collection": "poems"},
  )
  print(memory_status.resource_id)
  ```

  ```typescript TypeScript theme={null}
  const memoryStatus = await client.memories.add({
      text: '"'Twas brillig, and the slithy toves did gyre and gimble in the wabe..."',
      metadata: { collection: 'poems' }
  });
  console.log(memoryStatus.resource_id);
  ```
</CodeGroup>

The `resource_id` returned will be the ID of the memory. You can use this ID to retrieve the original document later. Some types of documents may take several seconds to process, so you may need to wait for the document to be processed before you can query it.

## Bulk Memory Ingestion

For adding multiple memories at once, use the bulk ingestion endpoint. This is more efficient than making multiple individual requests and ensures all memories are validated together before any database operations occur.

<CodeGroup>
  ```python Python theme={null}
  memory_statuses = client.memories.add_bulk(
      items=[
          {
              "text": "Two roads diverged in a yellow wood, And sorry I could not travel both...",
              "title": "The Road Not Taken",
              "metadata": {"author": "Robert Frost", "year": 1916, "collection": "poems"}
          },
          {
              "text": "Because I could not stop for Death – He kindly stopped for me...",
              "title": "Because I could not stop for Death",
              "metadata": {"author": "Emily Dickinson", "year": 1890, "collection": "poems"}
          },
          {
              "text": "I wandered lonely as a cloud That floats on high o'er vales and hills...",
              "title": "Daffodils",
              "metadata": {"author": "William Wordsworth", "year": 1807, "collection": "poems"}
          }
      ]
  )
  print(f"Successfully added {memory_statuses.count} memories")
  for item in memory_statuses.items:
      print(f"  {item.resource_id} - {item.status}")
  ```

  ```typescript TypeScript theme={null}
  const memoryStatuses = await client.memories.addBulk({
      items: [
          {
              text: 'Two roads diverged in a yellow wood, And sorry I could not travel both...',
              title: 'The Road Not Taken',
              metadata: { author: 'Robert Frost', year: 1916, collection: 'poems' }
          },
          {
              text: 'Because I could not stop for Death – He kindly stopped for me...',
              title: 'Because I could not stop for Death',
              metadata: { author: 'Emily Dickinson', year: 1890, collection: 'poems' }
          },
          {
              text: 'I wandered lonely as a cloud That floats on high o\'er vales and hills...',
              title: 'Daffodils',
              metadata: { author: 'William Wordsworth', year: 1807, collection: 'poems' }
          }
      ]
  });
  console.log(`Successfully added ${memoryStatuses.count} memories`);
  memoryStatuses.items.forEach(item => {
      console.log(`  ${item.resource_id} - ${item.status}`);
  });
  ```
</CodeGroup>

The bulk add endpoing can handle up to 100 items at a time. Each item follows the same validation rules as the single-item endpoint. The maximum request size is 10MB; requests exceeding this limit will receive a 413 (Payload Too Large) response.

### Error Handling

If validation fails for any item, the entire batch is rejected with a detailed error response:

```json theme={null}
{
  "message": "Validation failed for one or more items",
  "errors": [
    {
      "index": 1,
      "resource_id": "doc-2",
      "errors": [
        "Invalid collection name: 'bad;collection' contains unsafe characters"
      ]
    },
    {
      "index": 3,
      "resource_id": "doc-4",
      "errors": [
        "Invalid metadata: Key 'invalid-key!' contains invalid characters"
      ]
    }
  ]
}
```

The `index` field indicates which item in your batch (0-based) failed validation, allowing you to quickly identify and fix issues.

<Note>
  **When to use bulk ingestion:**

  * Adding multiple documents from a data import or sync process
  * Batch processing of user uploads
  * Migrating data from another system

  **When to use single-item ingestion:**

  * Adding individual memories in real-time
  * When you need immediate feedback for each item
  * For interactive user experiences
</Note>

## Querying memories

Once you have added a memory, you can query it using the `search` method.

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="what is a borogove?",
      sources=["vault"],
      options={
          "filter": {
              "collection": "poems"
          }
      }
  )
  print(response.documents)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: "what is a borogove?",
      sources=["vault"],
      options: {
          filter: {
              collection: "poems"
          }
      }
      });

  console.log(response.documents);
  ```
</CodeGroup>

As you can see, the `search` method takes a `query` string, and a `sources` parameter that lists the sources you want to query. In this example, we used `vault` as a source, which contains all documents added manually with the `/memories/add` endpoint.

Each data source comes with different options when querying data. In this example, we used the `vault` source, so in the `options` field we will find a key with the name of the source (`vault`) which contains all the options. In this case, we only have one option, which is the `filter` parameter. Note that if we didn't set this parameter, the entire vault will be searched.

## Debugging queries

They `/memories/query` endpoint is designed to always return a result to the best of Hyperspell's ability, even if some of the data sources might produce errors.

Each response from this endpoint contains an `errors` field, which contains a list of errors that occurred while querying the data sources. If no errors occurred, this field will be empty.

```json theme={null}
{
  "errors": [
    {
      "error": "ConnectionNotFound",
      "message": "User hasn't connected their slack account yet"
    }
  ],
  "documents": []
}
```

If you're not seeing the result you expected, it's a good idea to check the `errors` field for any errors that occurred — during development, we recommend logging the errors as warnings.

## Asking questions about your data

By default, the `search` will return the most relevant documents (or parts of documents) that match the query. Documents contain both structured data that you can use ie. in your UI to show results, and an LLM-summary that you can use for retrieval-augmented generation.

Of course, you can also use Hyperspell to answer questions about your document directly. To do so, simply include the `answer` parameter in your query:

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="which attacks does the jabberwock have?",
      sources=["vault"],
      answer=True,
      options={
          "filter": {
              "collection": "poems"
          }
      }
  )
  print(response.answer)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: "which attacks does the jabberwock have?",
      sources: ["vault"],
      answer: true,
      options: {
          filter: {
              collection: "poems"
          }
      }
  });

  console.log(response.answer);
  ```
</CodeGroup>

<Note>
  By default, Hyperspell uses a fine-tuned LLama 3.1 Instruct, 8B  instruct model to generate answers, which is by far one of the fastest models available for question-answering based on given documents.

  Your and your users' data is never used to train foundational models.

  If you need a more powerful model, please let us know and we'll add it to the platform. Of course, you can always bring your own model and only use Hyperspell for the retrieval part.
</Note>

## Querying multiple sources

You can query multiple sources at once by passing a list of sources to the `sources` parameter.

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="what did joe think about my poetry collection?",
      sources=["slack", "gmail"]
  )
  print(response.documents)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: "what did joe think about my poetry collection?",
      sources: ["slack", "gmail"]
  });

  console.log(response.documents);
  ```
</CodeGroup>

In this example, we're querying both the `slack` and `gmail` sources. Both sources are queried at the same time, and the results are merged together. You can use [Hyperspell Connect](/usage/connect) to let your users securely connect their accounts to Hyperspell, and then query their data.

## Choosing which answer model to use

By default, Hyperspell uses OpenAI's GPT-OSS 20B open-weight model to answer queries. This model is fast and efficient, but it may not be the best fit for all use cases.

If you need a more powerful model, you can choose a different model by passing the `answer_model` parameter to the `search` method.

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="which attacks does the jabberwock have?",
      sources=["vault"],
      answer=True,
      answer_model="deepseek-r1",
  )
  print(response.answer)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: "which attacks does the jabberwock have?",
      sources: ["vault"],
      answer: true,
      answer_model: "deepseek-r1",
  });

  console.log(response.answer);
  ```
</CodeGroup>

The following models are available:

| Value           | Name                | Use Case                                                                                                                   |
| --------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `gpt-oss-20b`   | OpenAI GPT-OSS 20B  | The default. General-purpose, fast open-weight model with strong reasoning for most RAG queries. Available in all regions. |
| `gpt-oss-120b`  | OpenAI GPT-OSS 120B | Larger GPT-OSS variant for harder queries where quality matters more than latency. Available in all regions.               |
| `llama-3.1`     | Meta Llama 3.1 8B   | General-purpose, fast, high-accuracy model that balances performance and efficiency for most English-language RAG queries. |
| `gemma2`        | Google Gemma 2      | Lightweight, fast model ideal for fast inference without sacrificing too much quality.                                     |
| `qwen-qwq`      | Alibaba  Qwen QWQ   | Multilingual or code-heavy queries where Chinese-language support or reasoning over technical content is important.        |
| `mistral-saba`  | Mistral Saba        | Small, open-weight model with strong performance in structured reasoning or concise summarization tasks.                   |
| `llama-4-scout` | Meta  Llama 4 Scout | State-of-the-art reasoning and nuanced understanding for complex or ambiguous queries.                                     |
| `deepseek-r1`   | DeepSeek R1         | Use this when your query involves math, code, or scientific reasoning.                                                     |

<Note>
  **EU region:** the GPT-OSS models are available in all regions. The other answer models run on US Bedrock inference profiles and are not available for EU-region accounts — selecting one returns an error rather than routing inference outside the EU. See [Regions & Data Residency](/concepts/data-residency).
</Note>

## Fine-tuning the query

There are multiple ways to influence which results the query produces:

### Scoping to specific resources

Use the `resource_ids` option to limit search results to specific documents. This is useful when you want to search within a particular email thread, uploaded file, or any other specific resource:

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="action items from the meeting",
      sources=["vault"],
      options={
          "resource_ids": ["meeting-2024-q1", "meeting-2024-q2"]
      }
  )
  print(response.documents)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'action items from the meeting',
      sources: ['vault'],
      options: {
          resource_ids: ['meeting-2024-q1', 'meeting-2024-q2']
      }
  });

  console.log(response.documents);
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.hyperspell.com/memories/query" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "action items from the meeting",
      "sources": ["vault"],
      "options": {
        "resource_ids": ["meeting-2024-q1", "meeting-2024-q2"]
      }
    }'
  ```
</CodeGroup>

You can combine `resource_ids` with other options like `filter`, `after`, and `before` to further narrow results. If omitted, all resources are searched.

### Setting the number of results

By default, Hyperspell will return 10 results. You can change this by passing the `max_results` parameter to query options:

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="what did joe think about my poetry collection?",
      sources=["slack", "gmail"],
      options={
          "max_results": 20
      }
  )
  print(response.documents)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: "what did joe think about my poetry collection?",
      sources: ["slack", "gmail"],
      options: {
          max_results: 20
      }
  });

  console.log(response.documents);
  ```
</CodeGroup>

This will both influence how many documents are returned, but also how many documents will be fed into the answer model if you set answer to `true`.

### Weighting data sources

In some cases, you may want to influence which data sources are used to answer a query. You can do this by passing the `weight` parameter to query option for each data source  :

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="what did joe think about my poetry collection?",
      sources=["slack", "gmail"],
      options={
          "slack": {
              "weight": 0.5
          },
          "gmail": {
              "weight": 1.5
          }
      }
  )
  print(response.documents)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: "what did joe think about my poetry collection?",
      sources: ["slack", "gmail"],
      options: {
          slack: {
              weight: 0.5
          },
          gmail: {
              weight: 1.5
          }
      }
  });

  console.log(response.documents);
  ```
</CodeGroup>

All weights will be normalized, so in this example, the `gmail` source will be weighted three times more than the `slack` source. The weights will be used internally by the re-ranker to influence which documents are returned and used for answering the query.
