---
slug: quickstart
title: Quickstart — your first vector search
kind: tutorial
surface: vectors
summary: Create an Aetherfy API key, install the Python or JS/TS SDK, create a collection, upsert points, and get search results back in one sitting.
sources:
  - aetherfy-vectors-python-sdk:aetherfy_vectors/client.py
  - aetherfy-vectors-python-sdk:aetherfy_vectors/models.py
  - aetherfy-vectors-python-sdk:setup.py
  - aetherfy-vectors-js-sdk:src/client.ts
  - aetherfy-vectors-js-sdk:src/models.ts
  - aetherfy-vectors-js-sdk:package.json
  - vectordb:backend/middleware/streamingPointsParser.js
---

import { Callout } from 'nextra/components'

# Quickstart — your first vector search

By the end of this page you will have created a collection in the Aetherfy
vector database, stored three documents in it, and printed ranked search
results. Every step is copy-pasteable in order, and the whole thing runs in one
sitting.

Aetherfy exposes a Qdrant-compatible vector API through a Python SDK, a JS/TS
SDK, and plain HTTP. This tutorial covers the two SDKs side by side — follow
whichever language you use; the steps are the same.

## Step 1 — Get your Aetherfy API key

Sign up or sign in at
[app.aetherfy.com/login](https://app.aetherfy.com/login) with email or GitHub.

On first login, the Aetherfy welcome modal reveals your default API key's
plaintext. **You see that plaintext exactly once.** Aetherfy never stores it
after that single reveal — only a hash of it — so nobody, including Aetherfy
support, can show it to you again. Copy it somewhere safe immediately.

If you lose the key, you cannot recover it; create a replacement at
[app.aetherfy.com/dashboard/settings/api-keys](https://app.aetherfy.com/dashboard/settings/api-keys).

<Callout type="warning">
  Your Aetherfy API key's plaintext is shown once, at first login, and is never
  stored afterwards. If you lose it, create a new key at
  [Settings → API keys](https://app.aetherfy.com/dashboard/settings/api-keys).
</Callout>

Aetherfy keys look like `afy_live_...` for live traffic, or `afy_test_...` for
test traffic. Export the key in your shell:

```bash
export AETHERFY_API_KEY="afy_live_your_api_key_here"
```

Both Aetherfy SDKs read the `AETHERFY_API_KEY` environment variable
automatically, so you never have to pass the key in code.

## Step 2 — Install an Aetherfy SDK

The Python and JS/TS clients for Aetherfy are published separately.

### Python

The distribution name is `aetherfy-vectors`; the import package is
`aetherfy_vectors`. Python 3.9 or newer is required.

```bash
pip install aetherfy-vectors
```

### JavaScript / TypeScript

The Aetherfy package is unscoped — it is `aetherfy-vectors`. Node 20 or newer is
required.

```bash
npm install aetherfy-vectors
```

## Step 3 — Construct the Aetherfy client

Constructing a client takes no arguments, because it reads `AETHERFY_API_KEY`
from the environment.

### Python

The Aetherfy Python client is synchronous — no `async`/`await` anywhere.

Note that `VectorConfig` and `DistanceMetric` are not re-exported from the root
package: import them from `aetherfy_vectors.models`.

```python
from aetherfy_vectors import AetherfyVectorsClient
from aetherfy_vectors.models import VectorConfig, DistanceMetric

client = AetherfyVectorsClient()   # reads AETHERFY_API_KEY
```

### JavaScript / TypeScript

Every method on the Aetherfy JS client is async and returns a promise.

```typescript
import { AetherfyVectorsClient, DistanceMetric } from 'aetherfy-vectors';

const client = new AetherfyVectorsClient();   // reads AETHERFY_API_KEY
```

## Step 4 — Bring your own embeddings to Aetherfy

Aetherfy stores and searches vectors; it does not generate them. You bring your
own embeddings from whichever model you already use.

Throughout this tutorial an `embed()` function stands in for that model. It is a
stub you fill in, and it must return a list of floats whose length matches the
`size` you give the collection in the next step. This tutorial uses 384, a
common embedding dimension — change it to match your model.

<Callout type="info">
  Aetherfy does not generate embeddings. Fill in the `embed()` stub with a call
  to your own embedding model, and make sure it returns exactly as many floats
  as the collection's `size`.
</Callout>

## Step 5 — Create an Aetherfy collection

A collection is the container that holds your points. It is created once, with a
fixed vector `size` and distance metric.

`size` must match the dimension of the embedding model behind `embed()`.
Aetherfy enforces no dimension cap. There are four metrics:
`DistanceMetric.COSINE`, `DistanceMetric.EUCLIDEAN`, `DistanceMetric.DOT`, and
`DistanceMetric.MANHATTAN`. Cosine is the usual choice for normalised text
embeddings; the [SDK reference](/vectors/sdk) lists what each one stores.

### Python

```python
client.create_collection(
    "quickstart",
    VectorConfig(size=384, distance=DistanceMetric.COSINE),
)
```

### JavaScript / TypeScript

```typescript
await client.createCollection('quickstart', {
  size: 384,
  distance: DistanceMetric.COSINE,
});
```

## Step 6 — Upsert points into Aetherfy

A point is an id, a vector, and an optional payload of metadata you can filter
on later. Upserting is idempotent by point id, so re-running the script updates
the same three points rather than duplicating them.

An Aetherfy point id is **an unsigned integer up to 2\*\*53 − 1, or a UUID
string** — nothing else. A slug such as `"doc-001"` is rejected by the SDK
validator before the request is sent, so use `1`, `2`, `3` or real UUIDs.

<Callout type="info">
  Point ids must be an unsigned integer up to 2\*\*53 − 1, or a UUID string. A
  slug like `"doc-001"` is rejected by the Aetherfy SDK validator.
</Callout>

Aetherfy accepts up to 10 000 points per upsert call.

### Python

```python
points = [
    {"id": 1, "vector": embed("some text"), "payload": {"text": "some text"}},
]
client.upsert("quickstart", points)
```

### JavaScript / TypeScript

```typescript
const points = [
  { id: 1, vector: await embed('some text'), payload: { text: 'some text' } },
];
await client.upsert('quickstart', points);
```

## Step 7 — Search your Aetherfy collection

This is the payoff. Searching an Aetherfy collection takes a query vector —
produced by the same embedding model that produced the stored vectors — and
returns the nearest points, best match first.

Each result carries `id`, `score`, `payload`, and `vector`. Ask for
`with_payload` / `withPayload` so the metadata comes back with the hit;
otherwise you get an id and a score and have to look the content up yourself.

### Python

Returns a `List[SearchResult]`, each with `.id`, `.score`, `.payload`, `.vector`.

```python
results = client.search(
    collection_name="quickstart",
    query_vector=embed("how do I filter by metadata?"),
    limit=5,
    with_payload=True,
)
```

### JavaScript / TypeScript

Returns a `SearchResult[]`, each with `id`, `score`, `payload`, `vector`.

```typescript
const results = await client.search('quickstart', queryVector, {
  limit: 5,
  withPayload: true,
});
```

## The complete Python script for Aetherfy

Save this as `aetherfy_quickstart.py`, fill in `embed()`, and run it with
`python aetherfy_quickstart.py`. It creates the collection, stores three short
documents, searches them, and prints ranked results.

```python
"""Aetherfy quickstart: create a collection, upsert documents, search them."""

import os

# Or export AETHERFY_API_KEY in your shell instead. An already-exported key wins.
os.environ.setdefault("AETHERFY_API_KEY", "afy_live_your_api_key_here")

from aetherfy_vectors import AetherfyVectorsClient
from aetherfy_vectors.models import VectorConfig, DistanceMetric

COLLECTION = "quickstart"
EMBEDDING_SIZE = 384


def embed(text: str) -> list:
    """Turn text into a list of EMBEDDING_SIZE floats.

    Aetherfy does not generate embeddings — you bring your own vectors.
    Replace this body with a call to your embedding model, and make sure it
    returns exactly EMBEDDING_SIZE floats.
    """
    raise NotImplementedError(f"Fill in embed() with your model; got: {text!r}")


DOCUMENTS = [
    {"id": 1, "text": "Cosine distance compares the angle between two vectors."},
    {"id": 2, "text": "A collection stores points, and every point has a vector."},
    {"id": 3, "text": "Payload fields let you filter search results by metadata."},
]


def main() -> None:
    client = AetherfyVectorsClient()  # reads AETHERFY_API_KEY

    client.create_collection(
        COLLECTION,
        VectorConfig(size=EMBEDDING_SIZE, distance=DistanceMetric.COSINE),
    )

    points = [
        {
            "id": doc["id"],
            "vector": embed(doc["text"]),
            "payload": {"text": doc["text"]},
        }
        for doc in DOCUMENTS
    ]
    client.upsert(COLLECTION, points)

    results = client.search(
        collection_name=COLLECTION,
        query_vector=embed("how do I filter by metadata?"),
        limit=5,
        with_payload=True,
    )

    for result in results:
        print(f"{result.score:.4f}  #{result.id}  {result.payload['text']}")

    client.close()


if __name__ == "__main__":
    main()
```

## The complete JavaScript/TypeScript script for Aetherfy

Save this as `aetherfy-quickstart.ts`, fill in `embed()`, and run it with your
usual TypeScript toolchain. The imports are ESM, so set `"type": "module"` in
your `package.json`. It creates the collection, stores three short documents,
searches them, and prints ranked results.

```typescript
/** Aetherfy quickstart: create a collection, upsert documents, search them. */
import { AetherfyVectorsClient, DistanceMetric } from 'aetherfy-vectors';

const COLLECTION = 'quickstart';
const EMBEDDING_SIZE = 384;

/**
 * Turn text into an array of EMBEDDING_SIZE floats.
 *
 * Aetherfy does not generate embeddings — you bring your own vectors. Replace
 * this body with a call to your embedding model, and make sure it returns
 * exactly EMBEDDING_SIZE floats.
 */
async function embed(text: string): Promise<number[]> {
  throw new Error(`Fill in embed() with your model; got: ${text}`);
}

const DOCUMENTS = [
  { id: 1, text: 'Cosine distance compares the angle between two vectors.' },
  { id: 2, text: 'A collection stores points, and every point has a vector.' },
  { id: 3, text: 'Payload fields let you filter search results by metadata.' },
];

async function main(): Promise<void> {
  const client = new AetherfyVectorsClient(); // reads AETHERFY_API_KEY

  await client.createCollection(COLLECTION, {
    size: EMBEDDING_SIZE,
    distance: DistanceMetric.COSINE,
  });

  const points = await Promise.all(
    DOCUMENTS.map(async (doc) => ({
      id: doc.id,
      vector: await embed(doc.text),
      payload: { text: doc.text },
    })),
  );
  await client.upsert(COLLECTION, points);

  const queryVector = await embed('how do I filter by metadata?');
  const results = await client.search(COLLECTION, queryVector, {
    limit: 5,
    withPayload: true,
  });

  for (const result of results) {
    console.log(
      `${result.score.toFixed(4)}  #${result.id}  ${result.payload?.text}`,
    );
  }

  client.dispose();
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
```

Three ranked results, best match first, means Aetherfy is working end to end.

## What's next with Aetherfy

- **[Examples](/examples)** — a cookbook of complete tasks, each in both Python
  and JS/TS, including [bringing your own embeddings](/examples/embeddings).
- **[SDK reference](/vectors/sdk)** — every method on both Aetherfy clients.
- **[Filtering](/vectors/filtering)** — narrow a search using the payload fields
  you stored alongside each vector.
- **[Limits](/platform/limits)** — the quotas and caps your Aetherfy plan
  applies.
- **[Deploy an agent](/agents/quickstart)** — run your own code on Aetherfy,
  as a service or as a scheduled task.
- **[Pricing](https://aetherfy.com/pricing)** — plans, and which of them include
  multi-region replication.
