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, the name of one of your aliases returns 409 COLLECTION_NAME_IS_AN_ALIAS, and a name that would be stored as one of your existing collections is (a ws_-prefixed collection outside a workspace from before that prefix was reserved) returns 409 COLLECTION_STORAGE_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.
A collection holds one unnamed vector per point: vectors is exactly {"size": 1 to 65536, "distance": "Cosine" | "Euclid" | "Dot" | "Manhattan"} (the distance in any case), and named vectors, any other key, or a size or distance outside those are refused with 400 VALIDATION_ERROR, field: "vectors".
# 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.
result also carries the collection’s payload indexes, as Qdrant’s
payload_schema ({field: {data_type, params?, points}}), and
payload_schema_region, the region whose Qdrant reported them. Aetherfy builds
an index in each of a collection’s regions on its own clock, so the indexes are
that region’s. A region that does not hold the collection answers null for
both, which means the indexes cannot be read there, not that there are none; a
collection with no indexes answers {}.
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/query | Similarity search. Body: {query:[...], limit?, offset?, filter?, with_payload?, with_vector?, score_threshold?, params?, prefetch?}; matches in result.points |
| POST | /api/v1/collections/{name}/points/query/batch | Several searches: {searches:[...]}, up to 100 |
| POST | /api/v1/collections/{name}/points/query/groups | Search grouped by a payload field (group_by) |
| 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.
Search is /points/query. Qdrant’s older search routes — /points/search,
/points/recommend, /points/discover, and their /batch and /groups forms —
answer 410 ROUTE_RETIRED, with the route to use in the body’s
replacement. A query body without query is not an error: it lists points
unranked, each scored 1.0.
On the retrieve route the flags are with_payload (default true) and
with_vector — singular — defaulting to false. The query 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 query 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/query \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":[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.
Reading from another of your collections in the Aetherfy vector API
Two query-body fields make the engine read a second collection:
| Field | Routes | What it does |
|---|---|---|
lookup_from | points/query, each of points/query/batch’s searches, points/query/groups, and every prefetch at any depth | {"collection": "<name>"}: point ids in query are turned into vectors from that collection instead of this one |
with_lookup | points/query/groups | A collection name, or {"collection": "<name>", "with_payload": ..., "with_vectors": ...}: each group’s id is looked up in that collection and returned as the group’s lookup |
Name the collection exactly as you would in the URL: its bare name, or
workspace/name for a collection in a workspace. Aetherfy scopes the name into
your account before the engine sees it. A name that belongs to another account
is refused 403 COLLECTION_OWNERSHIP_MISMATCH before the query runs, and a
value that is not a collection name (a number, a list, an empty string) is
refused 400 VALIDATION_ERROR. with_lookup spells its vector flag
with_vectors, plural, unlike the singular with_vector of the query body. The
SDKs do not expose either field; send them through the REST API.
# Search "articles" with the vector point 42 has in your "embeddings" collection.
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/query \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":42,"lookup_from":{"collection":"embeddings"},"limit":5,"with_payload":true}'
# Group "chunks" by doc_id and return each group's document from "documents".
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/chunks/points/query/groups \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":[0.1,0.2,0.3,0.4],"group_by":"doc_id","limit":3,"with_lookup":{"collection":"documents","with_payload":true,"with_vectors":false}}'
# The lookup collection lives in the workspace "team-alpha".
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/query \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":42,"lookup_from":{"collection":"team-alpha/embeddings"},"limit":5}'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.
An overwrite with key is refused with 400 KEYED_OVERWRITE_NOT_SUPPORTED, because the vector store would replace the whole payload rather than the value under key; to change a nested value, use POST with key.
# 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 returns 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 or delete all re-attribute the point to whoever performed that write — the same last-writer rule Aetherfy uses to order writes across regions.
Aetherfy records who made a write in the same request as the write. A payload
set, an overwrite and a payload clear are one operation, so a filter on them
selects the points once and the record lands on exactly those points. A keyed
set (with key), a payload-key delete and a vector delete are two steps — your
change, and the record of who made it — and each selects with your filter (a
vector update is two steps as well, by point id only). When the write picks its
points with a filter instead of point ids,
Aetherfy orders the two so that both reach exactly the points the filter
selected before the write, whether the key is yours or an agent’s:
| The filter tests | What Aetherfy does |
|---|---|
A key the write changes, such as status in a delete of status, or the vector a vector delete removes | Records the author first, then makes your change |
__aetherfy_agent_id or __aetherfy_deployment_id | Makes your change first, then records the author |
| Neither | Makes your change, then records the author |
| Both | Refuses the write with 400 AMBIGUOUS_FILTERED_WRITE; nothing is applied |
On these two-step writes a filter with a shape Aetherfy does not recognise is refused the same way, rather than guessed at. To make such a change, edit the points by id, or split the request so that each part’s filter tests only one of the two.
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.
To tell agent-written points from yours, test the author with is_empty, never
is_null: a point written with one of your own keys may hold no author key at
all, because the payload merge cannot store a null, and is_empty matches a
missing key and a null alike. Reads still return the key as null.
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.
Collection aliases in the Aetherfy vector API
An alias is a second name for one of your collections, kept in the storage engine’s alias table. Two routes manage them:
| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/collections/aliases | Apply a batch of alias changes. Body: {"actions": [...]} |
| GET | /api/v1/collections/{name}/aliases | List the aliases that point at one collection |
Each action is an object holding one of three keys:
| Action | Fields | Effect |
|---|---|---|
create_alias | alias_name, collection_name | Point a new alias at the collection |
delete_alias | alias_name | Remove the alias. The collection is untouched |
rename_alias | old_alias_name, new_alias_name | Give an alias a new name |
curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/aliases \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"actions":[{"create_alias":{"alias_name":"articles-live","collection_name":"articles"}}]}'
curl -s https://vectors.aetherfy.com/api/v1/collections/articles/aliases \
-H "Authorization: Bearer $AETHERFY_API_KEY"Aetherfy checks every action before sending the batch on, so a refused action means
nothing in the batch was applied. A create_alias must name a collection that
exists on your account (404 COLLECTION_NOT_FOUND otherwise). An alias lives in the
same scope as its collection: name a workspace’s alias and collection
"<workspace>/<name>" in the body, and a workspaceless alias must point at a
workspaceless collection — mixing the two, or renaming an alias into another
workspace, is 400 CROSS_SCOPE_ALIAS_NOT_ALLOWED. The batch route has no
workspace-scoped twin; the list has one,
GET /api/v1/workspaces/{workspace}/collections/{name}/aliases.
The batch answers once the region holding the collection has applied it, wherever
the request landed, and Aetherfy then sends it to the collection’s other regions.
A delete or rename names no collection, so Aetherfy first looks up which
collection the alias points at in each region; if the alias is in no region it
could read and one region could not be read, the batch is refused with 503
ALIAS_TARGET_UNRESOLVED and nothing is applied. Retry it.
The list answers with the names you use: a workspace’s aliases and collection are named bare, without the workspace, as the workspace’s collection list names them. It holds only your own aliases of that one collection, and it comes from a region that holds the collection, wherever the request landed:
{"result": {"aliases": [{"alias_name": "articles-live", "collection_name": "articles"}]}, "status": "ok", "time": 0.0001}Only the batch and the list are alias routes: POST or PUT on
/api/v1/collections/{name}/aliases is 405 METHOD_NOT_ALLOWED, and any
/api/v1/collections/{name}/aliases/{alias} is 404 NOT_FOUND, each with a message
naming the batch route.
Address points through an alias exactly as through the collection. Every point route takes an alias in place of the collection name, workspace-scoped forms included: query, scroll, count, retrieve, upsert, delete, and the payload and vector routes. Aetherfy serves the request as one naming the collection the alias points at when the request is made. It goes to a region that holds that collection, wherever it landed. A write is replicated to the collection’s regions, and it is counted against the collection’s points and storage. Swap the alias with a batch and the next request follows it.
A name is a collection or an alias, never both, across all your workspaces. A
create_alias or rename_alias that gives an alias the name of one of your
collections is 409 ALIAS_NAME_IS_A_COLLECTION, and creating a collection with the
name of one of your aliases is 409 COLLECTION_NAME_IS_AN_ALIAS, whichever
workspaces they are in. Outside a workspace, a collection or alias name may not
start with ws_ (400 RESERVED_NAME_PREFIX): that prefix is how Aetherfy writes a
workspace’s names.
A point request naming something that is neither a collection nor an alias of one
is 404 COLLECTION_NOT_FOUND. Aetherfy checks for an alias only when the name is
not one of your collections, and reads the alias lists only of the regions that
hold your collections. If one of them cannot be read, the request is 503
ALIAS_TARGET_UNRESOLVED. If Aetherfy cannot look up a collection at all, the
request is 503 PLACEMENT_UNAVAILABLE, never a not-found. Retry both. During a
database outage, collections that have not been accessed recently may be
unavailable for a short time; ones you have used recently keep being served. The list route does not
follow an alias: GET /api/v1/collections/{alias}/aliases is 404
COLLECTION_NOT_FOUND.
A read through an alias is never answered from Aetherfy’s response cache; only a read naming the collection is cached. After a write or a swap, a query through an alias answers as an uncached query naming the collection would.
A collection’s aliases are deleted, not carried over, when the collection moves between workspaces.
The alias codes, with their statuses, are on errors.
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/query | POST /api/v1/workspaces/{workspace}/collections/{name}/points/query |
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/query \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":[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 accepts the four distance names in any case at collection creation and stores them capitalised.
| You send (any case) | Aetherfy stores and returns |
|---|---|
cosine | Cosine |
euclid | Euclid |
dot | Dot |
manhattan | Manhattan |
Every name round-trips: a collection reads back the name it was created with,
capitalised. euclidean is not a distance name; a create that sends it is
refused with 400 VALIDATION_ERROR, field: "vectors". Both SDKs’
DistanceMetric.EUCLIDEAN sends Euclid.
Qdrant compatibility and forwarding in the Aetherfy vector API
Anything under /api/v1/collections/{name}/points/** is forwarded to Qdrant by
Aetherfy with its body unchanged, except where the body names a collection:
those names are scoped into your account (see
reading from another of your collections).
That 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 |
POST /collections/{name}/points/batch (Qdrant’s batch update) | 405 METHOD_NOT_ALLOWED, nothing applied | One request per write: PUT .../points, POST .../points/delete, POST or PUT .../points/payload, POST .../points/payload/delete, POST .../points/payload/clear, PUT .../points/vectors, POST .../points/vectors/delete |
| 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.