Skip to content

feat(serverless): OpenAPI spec for the collections REST API - #496

Open
generall wants to merge 1 commit into
mainfrom
serverless/collections-openapi
Open

generall wants to merge 1 commit into
mainfrom
serverless/collections-openapi

Conversation

@generall

@generall generall commented Sep 18, 2026

Copy link
Copy Markdown
Member

PREVIEW

What

Adds openapi/qdrant/serverless/collections/collections.json, a hand-written OpenAPI 3.0.3 description of the REST face of qdrant.serverless.CollectionsService (proto/qdrant/serverless/collections.proto).

The generated gen/openapiv2/.../collections.swagger.json is empty (the proto has no google.api.http annotations, by design), and a 1:1 gRPC transcoding would give POST /qdrant.serverless.CollectionsService/CreateCollection with dense_vectors: { "": { ... } } bodies. Instead this spec models the API the way the Qdrant server describes its own collections API, so anyone who knows Qdrant feels at home and can copy from its docs.

The REST surface

Method Path gRPC Notes
GET /collections?limit=&offset= ListCollections offset / next_page_offset are the opaque cursor (offset_token / next_offset_token in proto), named after Qdrant's scroll API
PUT /collections/{collection_name} CreateCollection body is the CollectionConfig; idempotent, result.created is false when it already existed
GET /collections/{collection_name} GetCollection 404 instead of exists: false
DELETE /collections/{collection_name} DeleteCollection result: true; 404 if missing
GET /collections/{collection_name}/exists GetCollection Qdrant-style probe, never 404

Design choices borrowed from the Qdrant server OpenAPI:

  • { "result": ..., "status": "ok", "time": ... } envelope and { "status": { "error": "..." } } error body, so responses look like the points/search API served on the same space.
  • vectors is either a single unnamed vector ({ "size", "distance" }) or a map of named ones, exactly like VectorsConfig. The proto's "" map key is never visible.
  • Enum values use Qdrant spelling: Cosine/Euclid/Dot/Manhattan, whitespace/word/..., sparse modifier: "idf" instead of use_idf: true.
  • Payload indexes accept the bare type ("user_id": "keyword") or an object with options ({ "type": "text", "tokenizer": "word" }), like PayloadFieldSchema. Keyword prefix is a boolean rather than an empty-message presence flag; the stemmer is a type-discriminated oneOf; stopwords accepts a single language string or a { languages, custom } set.
  • precision_tierprecision (low / medium / high), point_countpoints_count.
  • objects_deleted from DeleteCollectionResponse is not exposed: it counts storage objects, which is an implementation detail of the manager.

Examples

Create a hybrid-search collection with payload indexes:

curl -X PUT 'https://<your-space-endpoint>/collections/documents' \
  -H 'api-key: <YOUR_SPACE_API_KEY>' \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "vectors": {
      "text": { "size": 1024, "distance": "Cosine", "precision": "medium" }
    },
    "sparse_vectors": {
      "bm25": { "modifier": "idf" }
    },
    "payload_indexes": {
      "user_id": "keyword",
      "created_at": "datetime",
      "price": { "type": "integer", "lookup": false, "range": true },
      "title": {
        "type": "text",
        "tokenizer": "word",
        "stopwords": "english",
        "stemmer": { "type": "snowball", "language": "english" }
      }
    }
  }'
{ "result": { "name": "documents", "created": true }, "status": "ok", "time": 0.031 }

The simplest possible collection, and a ColBERT-style one:

curl -X PUT 'https://<your-space-endpoint>/collections/simple' -H 'api-key: ...' \
  --data-raw '{ "vectors": { "size": 1536, "distance": "Cosine" } }'

curl -X PUT 'https://<your-space-endpoint>/collections/colbert' -H 'api-key: ...' \
  --data-raw '{
    "vectors": { "colbert": { "size": 128, "distance": "Dot", "multivector": true, "precision": "low" } },
    "payload_indexes": { "doc_id": "uuid", "page": "integer" }
  }'

Read it back, list with pagination, delete:

curl 'https://<your-space-endpoint>/collections/documents' -H 'api-key: ...'
{
  "result": {
    "name": "documents",
    "config": {
      "vectors": { "text": { "size": 1024, "distance": "Cosine", "multivector": false, "precision": "medium" } },
      "sparse_vectors": { "bm25": { "modifier": "idf", "precision": "high" } },
      "payload_indexes": {
        "user_id": { "type": "keyword", "prefix": false },
        "created_at": { "type": "datetime" }
      }
    },
    "points_count": 12873
  },
  "status": "ok",
  "time": 0.004
}
curl 'https://<your-space-endpoint>/collections?limit=2' -H 'api-key: ...'
# → { "result": { "collections": [ { "name": "documents", "points_count": 12873 }, { "name": "images", "points_count": 4096 } ],
#                 "next_page_offset": "eyJsYXN0IjoiaW1hZ2VzIn0" }, "status": "ok", "time": 0.003 }
curl 'https://<your-space-endpoint>/collections?limit=2&offset=eyJsYXN0IjoiaW1hZ2VzIn0' -H 'api-key: ...'

curl -X DELETE 'https://<your-space-endpoint>/collections/documents' -H 'api-key: ...'
# → { "result": true, "status": "ok", "time": 0.12 }

The same walkthrough is embedded in info.description, so it renders at the top of the Redoc page as it does for the Qdrant server API. The PUT request body also carries three named examples (single_dense, hybrid, multivector).

Open questions for review

  • Auth header. The spec mirrors Qdrant (api-key header, plus Authorization: Bearer). Confirm this matches what the serverless auth sidecar actually accepts.
  • Server URL. Modelled as a {space_endpoint} variable (the SpaceEndpoint.url from the space API). If spaces have a predictable hostname pattern we can put it in the default.
  • Renames vs. proto (offset, precision, modifier, points_count, boolean prefix, Cosine casing). They are deliberate so the REST API reads like Qdrant, but the REST layer needs to translate them; happy to pull any of them back to the proto names.
  • HTTP status codes. 404 for GET/DELETE on a missing collection, idempotent 200 on re-create. The proto instead returns exists: false / deleted: false.
  • No PATCH (update) yet, since the proto has no UpdateCollection. The path is reserved for it.

Validation

  • openapi-spec-validator: OK
  • redocly lint (recommended ruleset): valid, no warnings
  • Every embedded example validated against its schema; lowercase "cosine" and unknown index types are rejected as intended.

🤖 Generated with Claude Code

Hand-written OpenAPI 3.0 counterpart to proto/qdrant/serverless/collections.proto.
Rather than transcoding the gRPC shapes one to one, the REST surface follows the
conventions of the Qdrant server collections API: PUT/GET/DELETE on
/collections/{collection_name}, a /exists probe, the {result, status, time}
envelope, qdrant-style enum values, single-or-named `vectors`, and payload
indexes given either as a bare type name or as an object with options.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

PR Packages Published

Python Package:

  • Version: 0.189.0.dev496+e2c2a63
  • Package: qdrant-cloud-public-api
  • Registry: https://us-python.pkg.dev/qdrant-cloud/python/
  • To update run: uv add qdrant-cloud-public-api==0.189.0.dev496+e2c2a63

NPM Package:

  • Version: 0.189.0-dev496.e2c2a63
  • Package: @qdrant/qdrant-cloud-public-api
  • Registry: https://us-npm.pkg.dev/qdrant-cloud/npm/
  • To update run: npm install @qdrant/qdrant-cloud-public-api@0.189.0-dev496.e2c2a63

@github-actions

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Pull Request / linting (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedSep 18, 2026, 4:00 PM

@generall
generall marked this pull request as ready for review September 18, 2026 17:14
@generall
generall requested a review from a team as a code owner September 18, 2026 17:14

@Robert-Stam Robert-Stam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants