---
slug: vectors/api
title: REST API reference
kind: reference
surface: vectors
summary: Every REST route of the Aetherfy vector API at https://vectors.aetherfy.com/api/v1 — collections, points, payload, schema, regions, the workspace-scoped forms, and the Qdrant endpoints Aetherfy deliberately does not support.
sources:
  - vectordb:backend/routes/proxy.js
  - vectordb:backend/routes/schema.js
  - vectordb:backend/routes/regions.js
  - vectordb:backend/middleware/resolveWorkspaceScope.js
  - vectordb:backend/utils/collectionValidation.js
  - vectordb:backend/utils/reservedFields.js
  - vectordb:backend/utils/catchAllAllowlist.js
  - dashboard/pages/api/proxy/collections.js
---

# Aetherfy vector REST API

## Base URL and authentication for the Aetherfy vector API

The Aetherfy vector API is served from `https://vectors.aetherfy.com`. Every
customer-facing route lives under `/api/v1`. Authentication is a bearer token:

```
Authorization: Bearer <api key>
```

Collection names are tenant-scoped by Aetherfy on the server side. You always
send your own bare collection name — never a prefixed or namespaced one — and
Aetherfy resolves it to your account's collection.

```bash
curl -s https://vectors.aetherfy.com/api/v1/collections \
  -H "Authorization: Bearer $AETHERFY_API_KEY"
```

A request with no `Authorization` header, or one that is not a `Bearer` header,
is refused with 401 `MISSING_API_KEY`. A syntactically valid key that does not
resolve is refused with 401 `INVALID_API_KEY`. All error shapes are documented on
[errors](/vectors/errors).

## Collection routes in the Aetherfy vector API

| Method | Path | Purpose |
|---|---|---|
| POST | `/api/v1/collections` | Create a collection. Body: `{name, vectors:{size,distance}, description?, regions?}`. Re-creating an existing collection **with the same configuration** is idempotent and returns 200; a different configuration under an existing name returns 409 `COLLECTION_NAME_TAKEN`. |
| GET | `/api/v1/collections` | List collections. Workspaceless form only. |
| GET | `/api/v1/collections/{name}` | Collection info. |
| PATCH | `/api/v1/collections/{name}` | Update metadata. `description` **only** — any other key returns 400. |
| DELETE | `/api/v1/collections/{name}` | Delete the collection. |

Aetherfy fixes the rest of the collection configuration server-side. There is no
request field for `optimizers_config`, `hnsw_config`, or any other engine tuning
at creation time.

Because a same-configuration create is idempotent on Aetherfy, a script may call
create unconditionally on every run without checking `collection_exists` first —
the second and later runs return 200 and change nothing. Only a create that
asks for a *different* configuration under a name already in use is an error.

```bash
# Create.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"articles","vectors":{"size":4,"distance":"Cosine"},"description":"Article embeddings"}'

# Read.
curl -s https://vectors.aetherfy.com/api/v1/collections/articles \
  -H "Authorization: Bearer $AETHERFY_API_KEY"

# Update the description (the only mutable field).
curl -s -X PATCH https://vectors.aetherfy.com/api/v1/collections/articles \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"description":"Article embeddings, v2"}'

# Delete.
curl -s -X DELETE https://vectors.aetherfy.com/api/v1/collections/articles \
  -H "Authorization: Bearer $AETHERFY_API_KEY"
```

Collection names are validated by Aetherfy: 1 to 100 characters, drawn from
`[a-zA-Z0-9_-]` only. Descriptions are at most 500 characters and may not contain
HTML angle brackets. Both violations return 400.

## Response headers and body on an Aetherfy collection read

`GET /api/v1/collections/{name}` returns a body of the form
`{result, schema_version}` and carries three response headers that Aetherfy adds:

| Header | Meaning |
|---|---|
| `ETag` | Version tag for the collection record |
| `X-Collection-Source` | Which store answered the read |
| `X-Collection-Status` | Lifecycle status of the collection |

These headers are specific to the single-collection read on Aetherfy; the list
route does not carry them.

## Point routes in the Aetherfy vector API

| Method | Path | Purpose |
|---|---|---|
| PUT | `/api/v1/collections/{name}/points` | **Upsert points (streaming).** Body: `{"points":[{id, vector, payload?}]}` |
| POST | `/api/v1/collections/{name}/points/search` | Similarity search |
| POST | `/api/v1/collections/{name}/points/retrieve` | Retrieve by ids. Body: `{ids:[...], with_payload?, with_vector?}` |
| GET | `/api/v1/collections/{name}/points/{id}` | Retrieve a single point |
| POST | `/api/v1/collections/{name}/points/scroll` | Paginate through points |
| POST | `/api/v1/collections/{name}/points/count` | Count. Body: `{filter?, exact?}` |
| POST | `/api/v1/collections/{name}/points/delete` | Delete by `{points:[ids]}` or by `{filter:{...}}` |

Upsert on Aetherfy is `PUT`, not `POST`. This is the single most common mistake
against this API — see the unsupported-endpoint section below for what `POST` on
that path actually does.

On the retrieve route the flags are `with_payload` (default `true`) and
`with_vector` — singular — defaulting to `false`. The search body takes the same
singular spelling, `with_vector`. Both Aetherfy SDKs expose this option under the
**plural** name (`with_vectors` in Python, `withVectors` in JavaScript), so a
hand-written REST search body that says `with_vectors` is not a flag Aetherfy
reads. On the count route a `limit`
key in the body is stripped by Aetherfy before the request reaches the engine, so
sending one has no effect.

```bash
# Upsert (PUT, not POST).
curl -s -X PUT https://vectors.aetherfy.com/api/v1/collections/articles/points \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"points":[
        {"id":1,"vector":[0.1,0.2,0.3,0.4],"payload":{"lang":"en"}},
        {"id":2,"vector":[0.4,0.3,0.2,0.1],"payload":{"lang":"fr"}}
      ]}'

# Search.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/search \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"vector":[0.1,0.2,0.3,0.4],"limit":5,"with_payload":true}'

# Retrieve by id.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/retrieve \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":[1,2],"with_payload":true,"with_vector":false}'

# Retrieve a single point.
curl -s https://vectors.aetherfy.com/api/v1/collections/articles/points/1 \
  -H "Authorization: Bearer $AETHERFY_API_KEY"

# Scroll.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/scroll \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"limit":100,"with_payload":true}'

# Count.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/count \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"exact":true}'

# Delete by id, then by filter.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/delete \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"points":[1,2]}'

curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/delete \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filter":{"must":[{"key":"lang","match":{"value":"fr"}}]}}'
```

Per-request caps on these routes — search and scroll `limit`, retrieve id count,
delete point count, upsert point count and wire size — are on
[limits](/vectors/limits).

## Payload and index routes in the Aetherfy vector API

| Method | Path | Purpose |
|---|---|---|
| POST | `/api/v1/collections/{name}/points/payload` | Set payload — merge |
| PUT | `/api/v1/collections/{name}/points/payload` | Overwrite payload — replace |
| POST | `/api/v1/collections/{name}/points/payload/delete` | Delete named payload keys |
| PUT or POST | `/api/v1/collections/{name}/index` | Create a payload field index |
| DELETE | `/api/v1/collections/{name}/index/{field}` | Delete a payload field index |

`POST` merges into the existing payload; `PUT` replaces it wholesale. Aetherfy
reserves every payload key beginning `__aetherfy_`: writing one returns 400
`RESERVED_FIELD`, and any such key is stripped from read responses.

```bash
# Merge keys into the existing payload.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/payload \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"reviewed":true},"points":[1,2]}'

# Replace the payload wholesale.
curl -s -X PUT https://vectors.aetherfy.com/api/v1/collections/articles/points/payload \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"lang":"en"},"points":[1]}'

# Remove named keys.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/payload/delete \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keys":["reviewed"],"points":[1,2]}'
```

## Schema routes in the Aetherfy vector API

| Method | Path | Purpose |
|---|---|---|
| GET | `/api/v1/schema/{collection}` | Read the stored payload schema |
| PUT | `/api/v1/schema/{collection}` | Write it. Body: `{schema, enforcement_mode, description?}` |
| DELETE | `/api/v1/schema/{collection}` | Remove it |
| POST | `/api/v1/schema/{collection}/analyze` | Infer a schema from stored points. Body: `{sample_size?}` |

`enforcement_mode` accepts `"off"`, `"warn"`, or `"strict"`; anything else returns
400 `INVALID_ENFORCEMENT_MODE`. `sample_size` must be between 100 and 10 000, or
Aetherfy returns 400 `INVALID_SAMPLE_SIZE`.

The PUT route supports `If-Match` for compare-and-set. A mismatch returns 412
`SCHEMA_VERSION_MISMATCH`, whose body carries `current_etag` so you can re-read
and retry.

```bash
# Read the current schema.
curl -s https://vectors.aetherfy.com/api/v1/schema/articles \
  -H "Authorization: Bearer $AETHERFY_API_KEY"

# Infer one from the stored points.
curl -s -X POST https://vectors.aetherfy.com/api/v1/schema/articles/analyze \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sample_size":500}'

# Remove it.
curl -s -X DELETE https://vectors.aetherfy.com/api/v1/schema/articles \
  -H "Authorization: Bearer $AETHERFY_API_KEY"
```

## Region discovery in the Aetherfy vector API

`GET /api/v1/regions` returns the region-to-regional-URL map that the Aetherfy
SDKs use to build a region-pinned client.

```bash
curl -s https://vectors.aetherfy.com/api/v1/regions \
  -H "Authorization: Bearer $AETHERFY_API_KEY"
```

Which regions actually hold your data is a property of your plan, not of this
route: Free and Starter accounts are single-region, with the region fixed by the
first resource created, and replication across regions begins at the tier named
**Performance**. Asking for a region outside your plan's scope returns 422
`STARTER_REGION_CONSISTENCY` or 422 `COLLECTION_REGIONS_NOT_IN_SCOPE`.

## Workspace-scoped routes in the Aetherfy vector API

Every collection route above has a workspace-scoped twin. Insert
`/workspaces/{workspace}` between `/api/v1` and `/collections`:

| Workspaceless | Workspace-scoped |
|---|---|
| `POST /api/v1/collections` | `POST /api/v1/workspaces/{workspace}/collections` |
| `GET /api/v1/collections/{name}` | `GET /api/v1/workspaces/{workspace}/collections/{name}` |
| `PATCH /api/v1/collections/{name}` | `PATCH /api/v1/workspaces/{workspace}/collections/{name}` |
| `DELETE /api/v1/collections/{name}` | `DELETE /api/v1/workspaces/{workspace}/collections/{name}` |
| `PUT /api/v1/collections/{name}/points` | `PUT /api/v1/workspaces/{workspace}/collections/{name}/points` |
| `POST /api/v1/collections/{name}/points/search` | `POST /api/v1/workspaces/{workspace}/collections/{name}/points/search` |

The collection name in the workspace-scoped form must be **bare** — it may not
contain a `/`. The collection list route (`GET /api/v1/collections`) exists in the
workspaceless form only.

```bash
curl -s -X POST https://vectors.aetherfy.com/api/v1/workspaces/research/collections/articles/points/search \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"vector":[0.1,0.2,0.3,0.4],"limit":5}'
```

Both Aetherfy SDKs reach the workspace-scoped routes through the `workspace`
client option rather than by hand-building the path — see [the SDK
reference](/vectors/sdk).

## Distance values accepted by the Aetherfy vector API

Aetherfy normalizes the distance string case-insensitively at collection
creation.

| You send (any case) | Aetherfy stores and returns |
|---|---|
| `cosine` | `Cosine` |
| `euclid` | `Euclid` |
| `euclidean` | **`Euclid`** |
| `dot` | `Dot` |
| `manhattan` | `Manhattan` |

Euclidean is the one value that does not round-trip through Aetherfy, and it
catches SDK users too: `DistanceMetric.EUCLIDEAN` sends the string `Euclidean`,
so a collection created with it reads back `config.distance == "Euclid"` and an
equality check against `Euclidean` silently fails. Every other name comes back
exactly as the table's left column, capitalised.

## Qdrant compatibility and forwarding in the Aetherfy vector API

Anything under `/api/v1/collections/{name}/points/**` is forwarded verbatim to
Qdrant by Aetherfy. Nothing inspects or rewrites the body, which is why Qdrant's
filter DSL, point shapes, and query bodies work unchanged — and why an
Aetherfy-side typo in a filter key is silently accepted rather than rejected.
Filter syntax and the one key-name defect worth knowing about are on
[filtering](/vectors/filtering).

The one consequence to internalise: Aetherfy validates the envelope (auth,
tenancy, caps, reserved keys), and Qdrant validates the query. An option Aetherfy
does not know about is not an error at the Aetherfy layer.

## Endpoints the Aetherfy vector API does not support

Aetherfy is Qdrant-compatible but not a Qdrant passthrough. These are refused,
deliberately, and no amount of retrying will change the answer.

| Request | Result | Use instead |
|---|---|---|
| `PUT /collections/{name}` (Qdrant-native create) | 405 `METHOD_NOT_ALLOWED` | `POST /api/v1/collections` |
| `PATCH /collections/{name}` with `optimizers_config`, `hnsw_config`, or any non-`description` key | 400 | Only `description` is mutable |
| `POST /collections/{name}/points` expecting upsert | Not upsert — falls through to Qdrant's batch retrieve, which expects `body.ids` | `PUT /api/v1/collections/{name}/points` |
| Snapshot endpoints | 404 "Unsupported endpoint" | — |
| `/telemetry`, `/metrics` | 404 "Unsupported endpoint" | — |
| `/cluster`, `/locks`, shard endpoints | 404 "Unsupported endpoint" | — |
| Collection creation with engine tuning fields | Not accepted — configuration is fixed server-side by Aetherfy | — |
| Any payload key beginning `__aetherfy_` | 400 `RESERVED_FIELD` on write; stripped from reads | Choose another key |

Query-time tuning is the one engine knob Aetherfy does expose, and it goes in the
request body's `params` field — see [search tuning](/vectors/search-tuning).
