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.
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.
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.
Deleting a collection an Aetherfy agent is using is refused with 409
COLLECTION_IN_USE. Nothing declares that: Aetherfy records which collections
each agent actually reads and writes, so a collection an agent opened at
runtime is protected exactly like one it has used since its first deploy. The
window is 35 days of activity — an agent that has not touched the collection in
longer no longer blocks it.
The response body carries an agents array naming the agents that block it. Stop
or delete those agents, or wait for the observation to age out, and repeat the
request. Every path that can destroy a collection runs this check — the API and
SDK delete, and Aetherfy’s own internal cleanup — so the refusal is a property
of the platform, not of the route you happened to call.
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.
# 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.
# 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.
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_ and strips them from read
responses, with two exceptions: __aetherfy_agent_id and
__aetherfy_deployment_id, the attested author and deployment described below,
come back on every read. Because they come back you may send them again —
Aetherfy ignores the values and re-stamps them from your credential. Sending any
of the others returns 400 RESERVED_FIELD.
# 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]}'Attested authorship in the Aetherfy vector API
Every point the Aetherfy vector API writes carries two keys in its payload that
record the credential that performed the write: __aetherfy_agent_id, which
agent, and __aetherfy_deployment_id, which of that agent’s deployments.
| Credential used for the write | __aetherfy_agent_id | __aetherfy_deployment_id |
|---|---|---|
| An API key Aetherfy minted and injected into an agent at deploy | That agent’s id | The deployment that wrote it; for a task agent, the run |
| An API key you created yourself | null | null |
| The Aetherfy dashboard’s vector explorer | null | null |
The two are set together: __aetherfy_deployment_id is null exactly when
__aetherfy_agent_id is. The deployment identifies a version of the agent, not
a machine or a region. Every machine of one service deployment stamps the same
id, and each task run stamps its own.
Aetherfy sets both values from the credential on every write. You may send the
keys back — you have to be able to, since they come out on every read and
editing a payload means retrieving it, changing a field and writing it again.
Aetherfy ignores whatever values you send and stamps the credential’s. Sending
them is never an error, and choosing their values is never possible: an agent
that names its own id or deployment gets the same treatment as one that names
somebody else’s, and a key you created yourself always stamps null for both no
matter what the body says. Both are attested by Aetherfy rather than asserted
by the client precisely because Aetherfy issued the agent’s credential for that
deployment and nobody else could have used it.
The other reserved keys behave differently, because they never come back on a
read: sending one is rejected with 400 RESERVED_FIELD.
Both keys are re-evaluated on every mutation. An upsert, a payload set or overwrite, a payload-key delete, a payload clear and a vector update all re-attribute the point to whoever performed that write — the same last-writer rule Aetherfy uses to order writes across regions.
Unlike Aetherfy’s other reserved keys, these two are returned on search, retrieve and scroll, and you can filter on either:
# Everything a given agent wrote.
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 '{"filter":{"must":[{"key":"__aetherfy_agent_id","match":{"value":"6f1c…"}}]},"limit":20,"with_payload":true}'Aetherfy does not create a payload index for these keys. An unindexed filter is scanned rather than looked up, so if you filter by author or deployment routinely, index the key yourself through the index route above. More filter forms, including “written by any agent” and “written by one deployment”, are on filtering.
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?} |
| POST | /api/v1/schema/{collection}/conformance | Report how much stored data would fail the schema. Body: {mode?, sample_size?, max_examples?} |
enforcement_mode accepts "off", "warn", or "strict"; anything else returns
400 INVALID_ENFORCEMENT_MODE. sample_size must be an integer between 100 and
10 000 on both analyze and conformance, or Aetherfy returns 400
INVALID_SAMPLE_SIZE.
Enforcement applies to new writes only, so a schema you switch on says nothing
about the points already stored. conformance is how you find out: it scans the
stored points against the stored schema and reports how many conform, which
fields are responsible, and a capped sample of offending point ids. It is
read-only and repairs nothing. mode is "sample" (the default, and it reads a
prefix of the collection rather than a random sample) or "full"; anything else
returns 400 INVALID_SCAN_MODE. max_examples caps the returned sample of
offending point ids and must be an integer between 0 and 100, or Aetherfy
returns 400 INVALID_MAX_EXAMPLES. Read
scan.exact before quoting the number: it is true only when the scan reached the
end of the collection, and a full scan of a large collection will usually stop at
its time budget and come back with scan.exact: false and
scan.stopped_because: "time_budget". A collection with no schema returns 404
SCHEMA_NOT_DEFINED, because there is nothing to conform to.
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.
# 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}'
# Ask how much of what is already stored would fail it.
curl -s -X POST https://vectors.aetherfy.com/api/v1/schema/articles/conformance \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode":"sample","sample_size":1000}'
# 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.
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 plan 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.
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.
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.
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 | — |
A payload key beginning __aetherfy_, other than __aetherfy_agent_id and __aetherfy_deployment_id | 400 RESERVED_FIELD on write; stripped from reads | Choose another key |
A payload key __aetherfy_agent_id or __aetherfy_deployment_id | Accepted and ignored — Aetherfy stamps the attested author and deployment from your credential; returned on reads | Nothing; round-tripping a payload is expected |
Query-time tuning is the one engine knob Aetherfy does expose, and it goes in the
request body’s params field — see search tuning.