First collection: upsert and search
The smallest useful Aetherfy program: create a collection, write points into it, and search it. Both versions below are complete files. The vectors are written out as literal floats so the example runs with no embedding model installed; in production you would produce them with an embedding model — see Bring your own embeddings.
Before you start, install the SDK and export a key from https://app.aetherfy.com/dashboard/settings/api-keys :
export AETHERFY_API_KEY="afy_live_your_key_here"Creating an Aetherfy collection in Python
Install with pip install aetherfy-vectors (Python >= 3.9).
VectorConfig and DistanceMetric are not re-exported from the package root, so import them from aetherfy_vectors.models.
from aetherfy_vectors import AetherfyVectorsClient
from aetherfy_vectors.models import VectorConfig, DistanceMetric, Point
# Reads AETHERFY_API_KEY from the environment. timeout is in seconds.
client = AetherfyVectorsClient(timeout=30.0)
collection = client.create_collection(
"products",
VectorConfig(size=4, distance=DistanceMetric.COSINE),
description="Product catalogue for the storefront",
)
print(collection.name) # products
print(collection.config.size) # 4
print(collection.config.distance) # DistanceMetric.COSINEsize is the dimension of every vector you will store in the collection and cannot be changed later. It must match the output dimension of whatever produces your vectors.
Re-running this file is safe. On Aetherfy, re-creating a collection with the same configuration is idempotent and returns 200, so the script calls create unconditionally rather than guarding it with collection_exists. Only a create that asks for a different configuration under a name already in use fails, with 409 COLLECTION_NAME_TAKEN.
Upserting points into an Aetherfy collection in Python
points = [
Point(id=1, vector=[0.10, 0.20, 0.30, 0.40],
payload={"title": "Wireless mouse", "category": "electronics", "price": 24.99}),
Point(id=2, vector=[0.15, 0.18, 0.33, 0.41],
payload={"title": "Mechanical keyboard", "category": "electronics", "price": 89.00}),
Point(id=3, vector=[0.90, 0.10, 0.05, 0.02],
payload={"title": "Ceramic mug", "category": "kitchen", "price": 12.50}),
]
ok = client.upsert("products", points)
print(ok) # Trueupsert returns a plain bool in the Aetherfy Python SDK — it does not return the written points. Writing a point with an id that already exists replaces it.
Searching an Aetherfy collection in Python
results = client.search(
"products",
query_vector=[0.12, 0.19, 0.31, 0.40],
limit=2,
with_payload=True,
)
for r in results:
print(r.id, r.payload)
# 1 {'title': 'Wireless mouse', 'category': 'electronics', 'price': 24.99}
# 2 {'title': 'Mechanical keyboard', 'category': 'electronics', 'price': 89.0}
client.close()Each element is a SearchResult dataclass with the fields listed in the shape table below. score is a float produced by the distance metric you chose for the collection; its magnitude depends entirely on your vectors, so do not hard-code a threshold without measuring against your own data.
Creating an Aetherfy collection in JavaScript
Install with npm install aetherfy-vectors (Node >= 20). Import everything from the package root; there is no aetherfy-vectors/… subpath export.
Save this as main.mjs, or set "type": "module" in your package.json.
import { AetherfyVectorsClient, DistanceMetric } from 'aetherfy-vectors';
// Reads AETHERFY_API_KEY from the environment. timeout is in milliseconds.
const client = new AetherfyVectorsClient({ timeout: 30000 });
async function main() {
const collection = await client.createCollection(
'products',
{ size: 4, distance: DistanceMetric.COSINE },
'Product catalogue for the storefront',
);
console.log(collection.name); // products
console.log(collection.config.size); // 4
console.log(collection.config.distance); // Cosine
}
main();Note the argument order difference: the Aetherfy JS createCollection(collectionName, vectorsConfig, description?, regions?) takes the description third and has no separate distance parameter — the distance lives inside the vectors config. The Python create_collection(collection_name, vectors_config, distance=None, description=None, regions=None) accepts an optional standalone distance.
Upserting points into an Aetherfy collection in JavaScript
import { AetherfyVectorsClient } from 'aetherfy-vectors';
const client = new AetherfyVectorsClient();
async function main() {
const ok = await client.upsert('products', [
{ id: 1, vector: [0.10, 0.20, 0.30, 0.40],
payload: { title: 'Wireless mouse', category: 'electronics', price: 24.99 } },
{ id: 2, vector: [0.15, 0.18, 0.33, 0.41],
payload: { title: 'Mechanical keyboard', category: 'electronics', price: 89.00 } },
{ id: 3, vector: [0.90, 0.10, 0.05, 0.02],
payload: { title: 'Ceramic mug', category: 'kitchen', price: 12.50 } },
]);
console.log(ok); // true
}
main();upsert resolves to a boolean in the Aetherfy JS SDK, matching the Python behaviour.
Searching an Aetherfy collection in JavaScript
Everything after the query vector goes in a single options object, unlike the positional/keyword mix in Python.
import { AetherfyVectorsClient } from 'aetherfy-vectors';
const client = new AetherfyVectorsClient();
async function main() {
const results = await client.search('products', [0.12, 0.19, 0.31, 0.40], {
limit: 2,
withPayload: true,
});
for (const r of results) {
console.log(r.id, r.payload);
}
// 1 { title: 'Wireless mouse', category: 'electronics', price: 24.99 }
// 2 { title: 'Mechanical keyboard', category: 'electronics', price: 89 }
client.dispose();
}
main();The full Aetherfy search options
| Python keyword | JavaScript option key | Default | Meaning |
|---|---|---|---|
limit | limit | 10 | Maximum number of results |
offset | offset | 0 | Skip this many results |
query_filter | queryFilter | None / undefined | Payload filter, see Filtered search |
with_payload | withPayload | True / true | Include the stored payload |
with_vectors | withVectors | False / false | Include the stored vector |
score_threshold | scoreThreshold | None / undefined | Drop results below this score |
search_params | searchParams | None / undefined | Engine-level search tuning, see Search tuning |
The Aetherfy response shapes
These are the model definitions, not a sample. Python uses snake_case dataclasses; JavaScript uses camelCase interfaces.
Point — what you send to upsert:
| Field | Python type | JavaScript type | Required |
|---|---|---|---|
id | Union[str, int] | string | number | yes |
vector | List[float] | number[] | yes |
payload | Optional[Dict] | Record<string, unknown> (optional) | no |
SearchResult — what search returns:
| Field | Python type | JavaScript type | Present when |
|---|---|---|---|
id | Union[str, int] | string | number | always |
score | float | number | always |
payload | Optional[Dict] | optional | with_payload / withPayload is true |
vector | Optional[List[float]] | optional | with_vectors / withVectors is true |
Collection — what create_collection and get_collection return.
| Python field | JavaScript field | Type |
|---|---|---|
name | name | string |
config | config | VectorConfig — size (int) and distance |
description | description | string, optional |
points_count | points_count | number, optional; None/undefined when the server does not report it |
status | status | optional |
regions | regions | optional; only populated on a plan with more than one region |
The Aetherfy distance metrics
DistanceMetric is an enum in both SDKs. Each member’s value is the name Aetherfy stores and returns, so all four round-trip.
| Enum member | Value sent, stored and returned |
|---|---|
DistanceMetric.COSINE | Cosine |
DistanceMetric.EUCLIDEAN | Euclid |
DistanceMetric.DOT | Dot |
DistanceMetric.MANHATTAN | Manhattan |
Pick the metric your embedding model was trained for. Cosine is the usual choice for normalised text embeddings.
Point id rules in Aetherfy
Both Aetherfy SDKs validate ids client-side, before any request goes out. Only two forms are accepted:
| Form | Accepted | Example |
|---|---|---|
Unsigned integer, 0 <= id <= 2**53 - 1 | yes | 1, 9007199254740991 |
| Canonical UUID string | yes | "3f1c2a7e-9b1e-4f2a-8c5d-1a2b3c4d5e6f" |
| UUID without hyphens (32 hex) | yes | "3f1c2a7e9b1e4f2a8c5d1a2b3c4d5e6f" |
| Braced UUID | yes | "{3f1c2a7e-9b1e-4f2a-8c5d-1a2b3c4d5e6f}" |
| URN UUID | yes | "urn:uuid:3f1c2a7e-9b1e-4f2a-8c5d-1a2b3c4d5e6f" |
| Arbitrary slug | no | "memory-001" raises ValidationError |
| Negative integer | no | -1 raises ValidationError |
| Boolean (Python) | no | True is rejected, it is not treated as 1 |
There is no coercion in either direction: an id you sent as an integer comes back as an integer, and an id you sent as a UUID string comes back as a string. If your domain keys are slugs such as order-12345, store the slug in the payload and use a UUID or an integer as the point id.
# Storing a domain key that is not a legal point id.
import uuid
client.upsert("products", [
Point(
id=str(uuid.uuid4()),
vector=[0.10, 0.20, 0.30, 0.40],
payload={"sku": "memory-001", "title": "Wireless mouse"},
)
])// The JavaScript equivalent, using the built-in crypto.randomUUID().
await client.upsert('products', [
{
id: crypto.randomUUID(),
vector: [0.10, 0.20, 0.30, 0.40],
payload: { sku: 'memory-001', title: 'Wireless mouse' },
},
]);Where Aetherfy puts this collection
Collection placement depends on your plan. On the Free and Starter plans a collection lives in a single region. Replication across more than one region begins at the plan named Performance, and only then does the regions argument to create_collection / createCollection and the regions field on Collection carry more than one entry. See Regions & replication.
Next steps with Aetherfy
- Narrow results by payload: Filtered search
- Write more than a handful of points at a time: Batch upsert & pagination
- Remove or count what you wrote: Retrieve, delete, count