---
slug: examples/collection-lifecycle
title: Collection lifecycle and payload schema
kind: howto
surface: vectors
summary: Complete Python and JavaScript examples for listing, checking, inspecting and deleting Aetherfy collections, plus payload mutation and the get/set/analyze schema calls with their enforcement modes.
sources:
  - aetherfy-vectors-python-sdk:aetherfy_vectors/client.py
  - aetherfy-vectors-python-sdk:aetherfy_vectors/schema.py
  - aetherfy-vectors-python-sdk:aetherfy_vectors/__init__.py
  - aetherfy-vectors-js-sdk:src/client.ts
  - aetherfy-vectors-js-sdk:src/models.ts
  - aetherfy-vectors-js-sdk:src/index.ts
---

# Collection lifecycle and payload schema

Everything that operates on a collection as a whole: listing what exists, checking for one, reading its configuration, deleting it — plus the payload mutation calls and the optional payload schema.

```bash
export AETHERFY_API_KEY="afy_live_your_key_here"
```

## The Aetherfy collection lifecycle methods

| Operation | Python | JavaScript | Returns |
| --- | --- | --- | --- |
| Create | `create_collection(collection_name, vectors_config, distance=None, description=None, regions=None)` | `createCollection(collectionName, vectorsConfig, description?, regions?)` | `Collection` |
| List all | `get_collections()` | `getCollections()` | `List[Collection]` / `Promise<Collection[]>` |
| Exists | `collection_exists(name)` | `collectionExists(name)` | `bool` / `Promise<boolean>` |
| Read one | `get_collection(name)` | `getCollection(name)` | `Collection` |
| Delete | `delete_collection(name)` | `deleteCollection(name)` | `bool` / `Promise<boolean>` |

Note the signature difference: the Aetherfy Python `create_collection` accepts a standalone `distance` argument in third position, while the JavaScript `createCollection` takes the description third and expects the distance inside the vectors config.

## Managing Aetherfy collections in Python

Install with `pip install aetherfy-vectors` (Python >= 3.9). `VectorConfig` and `DistanceMetric` live in `aetherfy_vectors.models` and are not re-exported from the package root; `Schema`, `FieldDefinition` and `AnalysisResult` are.

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

client = AetherfyVectorsClient()

# Create.
created = client.create_collection(
    "articles",
    VectorConfig(size=4, distance=DistanceMetric.COSINE),
    description="Published articles",
)
print(created.name, created.config.size, created.config.distance)
# articles 4 DistanceMetric.COSINE

# Exists.
print(client.collection_exists("articles"))   # True
print(client.collection_exists("nope"))       # False

# Read one.
info = client.get_collection("articles")
print(info.name)         # articles
print(info.config.size)  # 4
print(info.description)  # Published articles

# List all.
for c in client.get_collections():
    print(c.name, c.config.size, c.points_count)
# articles 4 0

# Delete.
print(client.delete_collection("articles"))   # True
print(client.collection_exists("articles"))   # False

client.close()
```

Creating a collection whose name is already taken, or reading one that does not exist, raises an `AetherfyVectorsException` subclass rather than returning `None`. Guard with `collection_exists` when the collection may or may not be there:

```python
if not client.collection_exists("articles"):
    client.create_collection("articles", VectorConfig(size=4, distance=DistanceMetric.COSINE))
```

## Managing Aetherfy collections in JavaScript

Install with `npm install aetherfy-vectors` (Node >= 20). Import everything from the package root.

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

const client = new AetherfyVectorsClient();

async function main() {
  // Create.
  const created = await client.createCollection(
    'articles',
    { size: 4, distance: DistanceMetric.COSINE },
    'Published articles',
  );
  console.log(created.name, created.config.size, created.config.distance);
  // articles 4 Cosine

  // Exists.
  console.log(await client.collectionExists('articles')); // true
  console.log(await client.collectionExists('nope'));     // false

  // Read one.
  const info = await client.getCollection('articles');
  console.log(info.name, info.config.size, info.description);
  // articles 4 Published articles

  // List all. Note pointsCount, not points_count.
  for (const c of await client.getCollections()) {
    console.log(c.name, c.config.size, c.pointsCount);
  }
  // articles 4 0

  // Delete.
  console.log(await client.deleteCollection('articles')); // true
  console.log(await client.collectionExists('articles')); // false

  client.dispose();
}

main();
```

## The Aetherfy Collection shape

| Python field | JavaScript field | Type | Notes |
| --- | --- | --- | --- |
| `name` | `name` | string | |
| `config` | `config` | `VectorConfig` | `size` (int) and `distance` |
| `description` | `description` | string, optional | |
| `points_count` | `pointsCount` | number, optional | Snake case in Python, camel case in JavaScript |
| `status` | `status` | optional | Reported by the server |
| `regions` | `regions` | optional | Populated only on a plan with more than one region |

Region placement is plan-scoped in Aetherfy: Free and Starter collections live in a single region, and replication across more than one region begins at the plan named Performance. The `regions` argument on create and the `regions` field here are only meaningful above that line. See [Regions & replication](/platform/regions).

## Mutating Aetherfy payloads without rewriting vectors

Three calls change a point's payload while leaving its vector alone. They differ in whether they merge, replace, or remove.

| Call | Python | JavaScript | Semantics |
| --- | --- | --- | --- |
| Merge | `set_payload(collection_name, payload, points, key=None)` | `setPayload(collectionName, payload, points, { key? })` | Additive — existing keys not mentioned are kept |
| Replace | `overwrite_payload(collection_name, payload, points)` | `overwritePayload(collectionName, payload, points)` | Full replace — keys not mentioned are dropped |
| Remove keys | `delete_payload(collection_name, keys, points)` | `deletePayload(collectionName, keys, points)` | Deletes the named keys only |

**Every payload mutation call is capped at 512 points per call in both SDKs.** Chunk a larger update.

### Payload mutation in Aetherfy with Python

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

client = AetherfyVectorsClient()

client.create_collection("articles", VectorConfig(size=4, distance=DistanceMetric.COSINE))
client.upsert("articles", [
    Point(id=1, vector=[0.1, 0.2, 0.3, 0.4],
          payload={"title": "Hello", "status": "draft", "author": "ada"}),
])

# Merge: adds published_at, keeps title/status/author, updates status.
client.set_payload("articles", {"status": "published", "published_at": "2026-01-01"}, [1])
print(client.retrieve("articles", [1])[0]["payload"])
# {'title': 'Hello', 'status': 'published', 'author': 'ada', 'published_at': '2026-01-01'}

# Remove specific keys.
client.delete_payload("articles", ["published_at"], [1])
print(client.retrieve("articles", [1])[0]["payload"])
# {'title': 'Hello', 'status': 'published', 'author': 'ada'}

# Replace: everything not listed is gone.
client.overwrite_payload("articles", {"title": "Hello"}, [1])
print(client.retrieve("articles", [1])[0]["payload"])
# {'title': 'Hello'}

# Chunk anything larger than 512 points.
ids = list(range(2000))
for start in range(0, len(ids), 512):
    client.set_payload("articles", {"batch": "import-7"}, ids[start:start + 512])

client.close()
```

### Payload mutation in Aetherfy with JavaScript

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

const client = new AetherfyVectorsClient();

async function main() {
  await client.createCollection('articles', { size: 4, distance: DistanceMetric.COSINE });
  await client.upsert('articles', [
    { id: 1, vector: [0.1, 0.2, 0.3, 0.4],
      payload: { title: 'Hello', status: 'draft', author: 'ada' } },
  ]);

  // Merge.
  await client.setPayload('articles', { status: 'published', published_at: '2026-01-01' }, [1]);
  console.log((await client.retrieve('articles', [1]))[0].payload);
  // { title: 'Hello', status: 'published', author: 'ada', published_at: '2026-01-01' }

  // Remove keys.
  await client.deletePayload('articles', ['published_at'], [1]);
  console.log((await client.retrieve('articles', [1]))[0].payload);
  // { title: 'Hello', status: 'published', author: 'ada' }

  // Replace.
  await client.overwritePayload('articles', { title: 'Hello' }, [1]);
  console.log((await client.retrieve('articles', [1]))[0].payload);
  // { title: 'Hello' }

  // Chunk anything larger than 512 points.
  const ids = Array.from({ length: 2000 }, (_, i) => i);
  for (let start = 0; start < ids.length; start += 512) {
    await client.setPayload('articles', { batch: 'import-7' }, ids.slice(start, start + 512));
  }

  client.dispose();
}

main();
```

## The Aetherfy payload schema

A collection can carry an optional schema describing the shape of its payloads. The schema is a set of named field definitions, and an *enforcement mode* decides what happens when a write does not match.

| Enforcement mode | Behaviour |
| --- | --- |
| `off` | The schema is stored but not applied. This is the default. |
| `warn` | Mismatches are surfaced but the write proceeds. |
| `strict` | Mismatches reject the write. |

Field definition fields:

| Python (`FieldDefinition`) | JavaScript (`FieldDefinition`) | Meaning |
| --- | --- | --- |
| `type` | `type` | One of `null`, `boolean`, `string`, `integer`, `float`, `array`, `object` |
| `required` | `required` | Whether the key must be present |
| `element_type` | `elementType` | Element type, for `array` fields |
| `fields` | `fields` | Nested field definitions, for `object` fields |

Note the casing split again: Python uses `element_type`, JavaScript uses `elementType`.

## Setting and reading an Aetherfy schema in Python

`Schema`, `FieldDefinition` and `AnalysisResult` are exported from the `aetherfy_vectors` package root.

```python
from aetherfy_vectors import AetherfyVectorsClient, Schema, FieldDefinition
from aetherfy_vectors.models import VectorConfig, DistanceMetric, Point

client = AetherfyVectorsClient()

client.create_collection("articles", VectorConfig(size=4, distance=DistanceMetric.COSINE))

schema = Schema(
    fields={
        "title": FieldDefinition(type="string", required=True),
        "status": FieldDefinition(type="string", required=True),
        "views": FieldDefinition(type="integer", required=False),
        "tags": FieldDefinition(type="array", required=False, element_type="string"),
    },
    description="Article payload contract",
)

etag = client.set_schema("articles", schema, enforcement="strict", description="v1")
print(etag)  # the ETag string of the new schema version

# Read it back. Returns None when no schema is set.
loaded = client.get_schema("articles")
print(sorted(loaded.fields))                 # ['status', 'tags', 'title', 'views']
print(loaded.fields["title"].type)           # string
print(loaded.fields["title"].required)       # True
print(loaded.fields["tags"].element_type)    # string

# Remove the schema.
print(client.delete_schema("articles"))      # True
print(client.get_schema("articles"))         # None

client.close()
```

`set_schema` returns the new schema's **ETag as a string**, not a schema object. `get_schema` returns `Optional[Schema]` — `None` means the collection exists but has no schema defined, which is a normal state and not an error.

The `enforcement` argument accepts only `"off"`, `"warn"` or `"strict"`; anything else raises before a request is made.

## Setting and reading an Aetherfy schema in JavaScript

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

const client = new AetherfyVectorsClient();

async function main() {
  await client.createCollection('articles', { size: 4, distance: DistanceMetric.COSINE });

  const schema: Schema = {
    fields: {
      title: { type: 'string', required: true },
      status: { type: 'string', required: true },
      views: { type: 'integer', required: false },
      tags: { type: 'array', required: false, elementType: 'string' },
    },
    description: 'Article payload contract',
  };

  const etag = await client.setSchema('articles', schema, 'strict', 'v1');
  console.log(etag); // the ETag string of the new schema version

  const loaded = await client.getSchema('articles');
  if (loaded) {
    console.log(Object.keys(loaded.fields).sort()); // [ 'status', 'tags', 'title', 'views' ]
    console.log(loaded.fields.title.type);          // string
    console.log(loaded.fields.title.required);      // true
    console.log(loaded.fields.tags.elementType);    // string
  }

  console.log(await client.deleteSchema('articles')); // true
  console.log(await client.getSchema('articles'));    // null
  client.dispose();
}

main();
```

`getSchema` resolves to `Schema | null`; `null` means no schema is defined. The enforcement mode is the third positional argument and defaults to `'off'`.

## Inferring an Aetherfy schema from existing points

`analyze_schema` / `analyzeSchema` samples the points already in a collection and proposes a schema. It writes nothing — you decide whether to install the suggestion.

`sample_size` must be between 100 and 10000 inclusive; the default is 1000.

### Analyzing in Python

```python
from aetherfy_vectors import AetherfyVectorsClient

client = AetherfyVectorsClient()

result = client.analyze_schema("articles", sample_size=1000)

print(result.collection)           # articles
print(result.sample_size)          # number of points actually sampled
print(result.total_points)         # points in the collection
print(result.processing_time_ms)   # server-side processing time, integer ms
print(sorted(result.suggested_schema.fields))
# ['status', 'tags', 'title', 'views']

# Install the suggestion, still without enforcing it.
etag = client.set_schema("articles", result.suggested_schema, enforcement="off")

client.close()
```

### Analyzing in JavaScript

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

const client = new AetherfyVectorsClient();

async function main() {
  const result = await client.analyzeSchema('articles', 1000);

  console.log(result.collection);        // articles
  console.log(result.sampleSize);        // number of points actually sampled
  console.log(result.totalPoints);       // points in the collection
  console.log(result.processingTimeMs);  // server-side processing time, integer ms
  console.log(Object.keys(result.suggestedSchema.fields).sort());
  // [ 'status', 'tags', 'title', 'views' ]

  await client.setSchema('articles', result.suggestedSchema, 'off');
  client.dispose();
}

main();
```

## The Aetherfy AnalysisResult shape

| Python field | JavaScript field | Type |
| --- | --- | --- |
| `collection` | `collection` | string |
| `sample_size` | `sampleSize` | integer |
| `total_points` | `totalPoints` | integer |
| `fields` | `fields` | map of field name to per-field analysis |
| `suggested_schema` | `suggestedSchema` | `Schema` |
| `processing_time_ms` | `processingTimeMs` | integer milliseconds |

A safe rollout is: `analyze_schema` to get a suggestion, `set_schema` with `off` to record it, switch to `warn` while you watch for mismatches, then `strict` once writes are clean.

Related: [First collection](/examples/first-collection) for creating and filling a collection, [Retrieve, delete, count](/examples/retrieve-delete-count) for removing points rather than the whole collection, and the [SDK reference](/vectors/sdk) for the complete method list.
