diff --git a/qdrant-landing/content/documentation/headless/content/tutorials/operations.md b/qdrant-landing/content/documentation/headless/content/tutorials/operations.md
index 849d21640..a2aeecad1 100644
--- a/qdrant-landing/content/documentation/headless/content/tutorials/operations.md
+++ b/qdrant-landing/content/documentation/headless/content/tutorials/operations.md
@@ -2,8 +2,9 @@
| :--- | :--- | :--- | :--- | :--- |
| [Snapshots](/documentation/tutorials-operations/create-snapshot/) | Create and restore collection snapshots. | Python | 20m | Beginner |
| [Data Migration](/documentation/tutorials-operations/migration/) | Move embeddings to Qdrant. | CLI | 30m | Intermediate |
-| [Embedding Model Migration](/documentation/tutorials-operations/embedding-model-migration/) | Use your new model with zero downtime. | None | 40m | Intermediate |
-| [Time-Based Sharding](/documentation/tutorials-operations/time-based-sharding/) | Efficiently manage time-series data with user-defined sharding. | None | 1h | Intermediate |
-| [Large-Scale Search](/documentation/tutorials-operations/large-scale-search/) | Cost-efficient search for LAION-400M datasets. | None | 48h | Advanced |
+| [Embedding Model Migration](/documentation/tutorials-operations/embedding-model-migration/) | Use your new model with zero downtime. | Any | 40m | Intermediate |
+| [Time-Based Sharding](/documentation/tutorials-operations/time-based-sharding/) | Efficiently manage time-series data with user-defined sharding. | Any | 1h | Intermediate |
+| [Large-Scale Search](/documentation/tutorials-operations/large-scale-search/) | Cost-efficient search for LAION-400M datasets. | Any | 48h | Advanced |
+| [Secure a Self-Hosted Instance](/documentation/tutorials-operations/secure-qdrant/) | Enable TLS, API keys, and JWT access control. | Any | 45m | Intermediate |
| [Qdrant Cloud Prometheus Monitoring](/documentation/ops-monitoring/managed-cloud-prometheus/) | Observability with Prometheus and Grafana. | Prometheus | 30m | Intermediate |
| [Self-Hosted Prometheus Monitoring](/documentation/ops-monitoring/hybrid-cloud-prometheus/) | Observability for hybrid/private cloud setups. | Prometheus | 30m | Intermediate |
\ No newline at end of file
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/csharp.cs b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/csharp.cs
new file mode 100644
index 000000000..38ff67021
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/csharp.cs
@@ -0,0 +1,93 @@
+// @block-start upsert-no-auth
+using Qdrant.Client;
+using Qdrant.Client.Grpc;
+
+public class Snippet
+{
+ public static async Task Run()
+ {
+ var client = new QdrantClient(host: "localhost", port: 6334, https: true);
+
+ try
+ {
+ await client.CreateCollectionAsync(
+ collectionName: "my_collection",
+ vectorsConfig: new VectorParams { Size = 4, Distance = Distance.Cosine }
+ );
+
+ await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 1, Vectors = new[] { 0.1f, 0.2f, 0.3f, 0.4f } }
+ }
+ );
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message); // Unauthenticated
+ }
+ // @block-end upsert-no-auth
+
+ // @block-start upsert-admin-key
+ client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "my-admin-key");
+
+ await client.CreateCollectionAsync(
+ collectionName: "my_collection",
+ vectorsConfig: new VectorParams { Size = 4, Distance = Distance.Cosine }
+ );
+
+ await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 1, Vectors = new[] { 0.1f, 0.2f, 0.3f, 0.4f } }
+ }
+ );
+ // @block-end upsert-admin-key
+
+ // @block-start delete-read-only-key
+ client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "my-read-only-key");
+
+ try
+ {
+ await client.DeleteAsync(collectionName: "my_collection", ids: (ulong[])[1]);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message); // PermissionDenied
+ }
+ // @block-end delete-read-only-key
+
+ // @block-start upsert-jwt-rw-collection
+ client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "");
+
+ await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 2, Vectors = new[] { 0.5f, 0.6f, 0.7f, 0.8f } }
+ }
+ );
+ // @block-end upsert-jwt-rw-collection
+
+ // @block-start upsert-jwt-ro-collection
+ client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "");
+
+ try
+ {
+ await client.UpsertAsync(
+ collectionName: "other_collection",
+ points: new List
+ {
+ new() { Id = 2, Vectors = new[] { 0.5f, 0.6f, 0.7f, 0.8f } }
+ }
+ );
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message); // PermissionDenied
+ }
+ // @block-end upsert-jwt-ro-collection
+ }
+}
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/csharp.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/csharp.md
new file mode 100644
index 000000000..49eeec401
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/csharp.md
@@ -0,0 +1,79 @@
+```csharp
+using Qdrant.Client;
+using Qdrant.Client.Grpc;
+
+var client = new QdrantClient(host: "localhost", port: 6334, https: true);
+
+try
+{
+ await client.CreateCollectionAsync(
+ collectionName: "my_collection",
+ vectorsConfig: new VectorParams { Size = 4, Distance = Distance.Cosine }
+ );
+
+ await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 1, Vectors = new[] { 0.1f, 0.2f, 0.3f, 0.4f } }
+ }
+ );
+}
+catch (Exception e)
+{
+ Console.WriteLine(e.Message); // Unauthenticated
+}
+
+client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "my-admin-key");
+
+await client.CreateCollectionAsync(
+ collectionName: "my_collection",
+ vectorsConfig: new VectorParams { Size = 4, Distance = Distance.Cosine }
+);
+
+await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 1, Vectors = new[] { 0.1f, 0.2f, 0.3f, 0.4f } }
+ }
+);
+
+client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "my-read-only-key");
+
+try
+{
+ await client.DeleteAsync(collectionName: "my_collection", ids: (ulong[])[1]);
+}
+catch (Exception e)
+{
+ Console.WriteLine(e.Message); // PermissionDenied
+}
+
+client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "");
+
+await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 2, Vectors = new[] { 0.5f, 0.6f, 0.7f, 0.8f } }
+ }
+);
+
+client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "");
+
+try
+{
+ await client.UpsertAsync(
+ collectionName: "other_collection",
+ points: new List
+ {
+ new() { Id = 2, Vectors = new[] { 0.5f, 0.6f, 0.7f, 0.8f } }
+ }
+ );
+}
+catch (Exception e)
+{
+ Console.WriteLine(e.Message); // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/csharp.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/csharp.md
new file mode 100644
index 000000000..50bb42f0e
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/csharp.md
@@ -0,0 +1,12 @@
+```csharp
+client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "my-read-only-key");
+
+try
+{
+ await client.DeleteAsync(collectionName: "my_collection", ids: (ulong[])[1]);
+}
+catch (Exception e)
+{
+ Console.WriteLine(e.Message); // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/go.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/go.md
new file mode 100644
index 000000000..43d314fa9
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/go.md
@@ -0,0 +1,19 @@
+```go
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "my-read-only-key",
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+_, err = client.Delete(context.Background(), &qdrant.DeletePoints{
+ CollectionName: "my_collection",
+ Points: qdrant.NewPointsSelector(qdrant.NewIDNum(1)),
+})
+if err != nil {
+ fmt.Println(err) // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/java.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/java.md
new file mode 100644
index 000000000..098ad5854
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/java.md
@@ -0,0 +1,12 @@
+```java
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("my-read-only-key")
+ .build());
+
+try {
+ client.deleteAsync("my_collection", List.of(id(1))).get();
+} catch (Exception e) {
+ System.out.println(e.getMessage()); // PERMISSION_DENIED
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/python.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/python.md
new file mode 100644
index 000000000..15ff4bb3a
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/python.md
@@ -0,0 +1,11 @@
+```python
+client = QdrantClient(url="https://localhost:6333", api_key="my-read-only-key")
+
+try:
+ client.delete(
+ collection_name="my_collection",
+ points_selector=models.PointIdsList(points=[1]),
+ )
+except Exception as e:
+ print(e) # 403 Forbidden
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/rust.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/rust.md
new file mode 100644
index 000000000..017b1b6ba
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/rust.md
@@ -0,0 +1,16 @@
+```rust
+let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("my-read-only-key")
+ .build()?;
+
+let result = client
+ .delete_points(
+ DeletePointsBuilder::new("my_collection").points(PointsIdsList {
+ ids: vec![1.into()],
+ }),
+ )
+ .await;
+if let Err(e) = result {
+ println!("{}", e); // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/typescript.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/typescript.md
new file mode 100644
index 000000000..71ace6679
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/delete-read-only-key/typescript.md
@@ -0,0 +1,9 @@
+```typescript
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "my-read-only-key" });
+
+try {
+ await client.delete("my_collection", { points: [1] });
+} catch (e: any) {
+ console.error(e.message); // 403 Forbidden
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/go.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/go.md
new file mode 100644
index 000000000..757c7f9b2
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/go.md
@@ -0,0 +1,127 @@
+```go
+import (
+ "context"
+ "fmt"
+
+ "github.com/qdrant/go-client/qdrant"
+)
+
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+client.CreateCollection(context.Background(), &qdrant.CreateCollection{
+ CollectionName: "my_collection",
+ VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
+ Size: 4,
+ Distance: qdrant.Distance_Cosine,
+ }),
+})
+
+_, err = client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(1),
+ Vectors: qdrant.NewVectors(0.1, 0.2, 0.3, 0.4),
+ },
+ },
+})
+if err != nil {
+ fmt.Println(err) // Unauthenticated
+}
+
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "my-admin-key",
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+client.CreateCollection(context.Background(), &qdrant.CreateCollection{
+ CollectionName: "my_collection",
+ VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
+ Size: 4,
+ Distance: qdrant.Distance_Cosine,
+ }),
+})
+
+client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(1),
+ Vectors: qdrant.NewVectors(0.1, 0.2, 0.3, 0.4),
+ },
+ },
+})
+
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "my-read-only-key",
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+_, err = client.Delete(context.Background(), &qdrant.DeletePoints{
+ CollectionName: "my_collection",
+ Points: qdrant.NewPointsSelector(qdrant.NewIDNum(1)),
+})
+if err != nil {
+ fmt.Println(err) // PermissionDenied
+}
+
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "",
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(2),
+ Vectors: qdrant.NewVectors(0.5, 0.6, 0.7, 0.8),
+ },
+ },
+})
+
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "",
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+_, err = client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "other_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(2),
+ Vectors: qdrant.NewVectors(0.5, 0.6, 0.7, 0.8),
+ },
+ },
+})
+if err != nil {
+ fmt.Println(err) // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/java.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/java.md
new file mode 100644
index 000000000..a8763f52b
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/java.md
@@ -0,0 +1,88 @@
+```java
+import static io.qdrant.client.PointIdFactory.id;
+import static io.qdrant.client.VectorsFactory.vectors;
+
+import io.qdrant.client.QdrantClient;
+import io.qdrant.client.QdrantGrpcClient;
+import io.qdrant.client.grpc.Collections.Distance;
+import io.qdrant.client.grpc.Collections.VectorParams;
+import io.qdrant.client.grpc.Points.PointStruct;
+import java.util.List;
+
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true).build());
+
+try {
+ client.createCollectionAsync("my_collection",
+ VectorParams.newBuilder()
+ .setSize(4)
+ .setDistance(Distance.Cosine)
+ .build()).get();
+
+ client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(1))
+ .setVectors(vectors(0.1f, 0.2f, 0.3f, 0.4f))
+ .build()
+ )).get();
+} catch (Exception e) {
+ System.out.println(e.getMessage()); // UNAUTHENTICATED
+}
+
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("my-admin-key")
+ .build());
+
+client.createCollectionAsync("my_collection",
+ VectorParams.newBuilder()
+ .setSize(4)
+ .setDistance(Distance.Cosine)
+ .build()).get();
+
+client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(1))
+ .setVectors(vectors(0.1f, 0.2f, 0.3f, 0.4f))
+ .build()
+)).get();
+
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("my-read-only-key")
+ .build());
+
+try {
+ client.deleteAsync("my_collection", List.of(id(1))).get();
+} catch (Exception e) {
+ System.out.println(e.getMessage()); // PERMISSION_DENIED
+}
+
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("")
+ .build());
+
+client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(2))
+ .setVectors(vectors(0.5f, 0.6f, 0.7f, 0.8f))
+ .build()
+)).get();
+
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("")
+ .build());
+
+try {
+ client.upsertAsync("other_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(2))
+ .setVectors(vectors(0.5f, 0.6f, 0.7f, 0.8f))
+ .build()
+ )).get();
+} catch (Exception e) {
+ System.out.println(e.getMessage()); // PERMISSION_DENIED
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/python.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/python.md
new file mode 100644
index 000000000..da19b9775
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/python.md
@@ -0,0 +1,57 @@
+```python
+from qdrant_client import QdrantClient, models
+
+client = QdrantClient(url="https://localhost:6333")
+
+try:
+ client.create_collection(
+ collection_name="my_collection",
+ vectors_config=models.VectorParams(size=4, distance=models.Distance.COSINE),
+ )
+
+ client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=1, vector=[0.1, 0.2, 0.3, 0.4])],
+ )
+except Exception as e:
+ print(e) # 401 Unauthorized
+
+client = QdrantClient(url="https://localhost:6333", api_key="my-admin-key")
+
+client.create_collection(
+ collection_name="my_collection",
+ vectors_config=models.VectorParams(size=4, distance=models.Distance.COSINE),
+)
+
+client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=1, vector=[0.1, 0.2, 0.3, 0.4])],
+)
+
+client = QdrantClient(url="https://localhost:6333", api_key="my-read-only-key")
+
+try:
+ client.delete(
+ collection_name="my_collection",
+ points_selector=models.PointIdsList(points=[1]),
+ )
+except Exception as e:
+ print(e) # 403 Forbidden
+
+client = QdrantClient(url="https://localhost:6333", api_key="")
+
+client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=2, vector=[0.5, 0.6, 0.7, 0.8])],
+)
+
+client = QdrantClient(url="https://localhost:6333", api_key="")
+
+try:
+ client.upsert(
+ collection_name="other_collection",
+ points=[models.PointStruct(id=2, vector=[0.5, 0.6, 0.7, 0.8])],
+ )
+except Exception as e:
+ print(e) # 403 Forbidden
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/rust.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/rust.md
new file mode 100644
index 000000000..87fe57ffd
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/rust.md
@@ -0,0 +1,87 @@
+```rust
+use qdrant_client::qdrant::{
+ CreateCollectionBuilder, DeletePointsBuilder, Distance, PointStruct, PointsIdsList,
+ UpsertPointsBuilder, VectorParamsBuilder,
+};
+use qdrant_client::Qdrant;
+
+let client = Qdrant::from_url("https://localhost:6334").build()?;
+
+let result = client
+ .create_collection(
+ CreateCollectionBuilder::new("my_collection")
+ .vectors_config(VectorParamsBuilder::new(4, Distance::Cosine)),
+ )
+ .await;
+if let Err(e) = result {
+ println!("{}", e); // Unauthorized
+}
+
+let result = client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(1, vec![0.1_f32, 0.2, 0.3, 0.4], [("source", "tutorial".into())])],
+ ))
+ .await;
+if let Err(e) = result {
+ println!("{}", e); // Unauthorized
+}
+
+let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("my-admin-key")
+ .build()?;
+
+client
+ .create_collection(
+ CreateCollectionBuilder::new("my_collection")
+ .vectors_config(VectorParamsBuilder::new(4, Distance::Cosine)),
+ )
+ .await?;
+
+client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(1, vec![0.1_f32, 0.2, 0.3, 0.4], [("source", "tutorial".into())])],
+ ))
+ .await?;
+
+let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("my-read-only-key")
+ .build()?;
+
+let result = client
+ .delete_points(
+ DeletePointsBuilder::new("my_collection").points(PointsIdsList {
+ ids: vec![1.into()],
+ }),
+ )
+ .await;
+if let Err(e) = result {
+ println!("{}", e); // PermissionDenied
+}
+
+let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("")
+ .build()?;
+
+client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(2, vec![0.5_f32, 0.6, 0.7, 0.8], [("source", "tutorial".into())])],
+ ))
+ .await?;
+
+let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("")
+ .build()?;
+
+let result = client
+ .upsert_points(UpsertPointsBuilder::new(
+ "other_collection",
+ vec![PointStruct::new(2, vec![0.5_f32, 0.6, 0.7, 0.8], [("source", "tutorial".into())])],
+ ))
+ .await;
+if let Err(e) = result {
+ println!("{}", e); // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/typescript.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/typescript.md
new file mode 100644
index 000000000..ed03d7d90
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/typescript.md
@@ -0,0 +1,51 @@
+```typescript
+import { QdrantClient } from "@qdrant/js-client-rest";
+
+client = new QdrantClient({ url: "https://localhost:6333" });
+
+try {
+ await client.createCollection("my_collection", {
+ vectors: { size: 4, distance: "Cosine" },
+ });
+
+ await client.upsert("my_collection", {
+ points: [{ id: 1, vector: [0.1, 0.2, 0.3, 0.4] }],
+ });
+} catch (e: any) {
+ console.error(e.message); // 401 Unauthorized
+}
+
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "my-admin-key" });
+
+await client.createCollection("my_collection", {
+ vectors: { size: 4, distance: "Cosine" },
+});
+
+await client.upsert("my_collection", {
+ points: [{ id: 1, vector: [0.1, 0.2, 0.3, 0.4] }],
+});
+
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "my-read-only-key" });
+
+try {
+ await client.delete("my_collection", { points: [1] });
+} catch (e: any) {
+ console.error(e.message); // 403 Forbidden
+}
+
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "" });
+
+await client.upsert("my_collection", {
+ points: [{ id: 2, vector: [0.5, 0.6, 0.7, 0.8] }],
+});
+
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "" });
+
+try {
+ await client.upsert("other_collection", {
+ points: [{ id: 2, vector: [0.5, 0.6, 0.7, 0.8] }],
+ });
+} catch (e: any) {
+ console.error(e.message); // 403 Forbidden
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/csharp.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/csharp.md
new file mode 100644
index 000000000..838e3b30c
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/csharp.md
@@ -0,0 +1,16 @@
+```csharp
+client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "my-admin-key");
+
+await client.CreateCollectionAsync(
+ collectionName: "my_collection",
+ vectorsConfig: new VectorParams { Size = 4, Distance = Distance.Cosine }
+);
+
+await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 1, Vectors = new[] { 0.1f, 0.2f, 0.3f, 0.4f } }
+ }
+);
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/go.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/go.md
new file mode 100644
index 000000000..16a9f0ab3
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/go.md
@@ -0,0 +1,29 @@
+```go
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "my-admin-key",
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+client.CreateCollection(context.Background(), &qdrant.CreateCollection{
+ CollectionName: "my_collection",
+ VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
+ Size: 4,
+ Distance: qdrant.Distance_Cosine,
+ }),
+})
+
+client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(1),
+ Vectors: qdrant.NewVectors(0.1, 0.2, 0.3, 0.4),
+ },
+ },
+})
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/java.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/java.md
new file mode 100644
index 000000000..aafa25774
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/java.md
@@ -0,0 +1,19 @@
+```java
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("my-admin-key")
+ .build());
+
+client.createCollectionAsync("my_collection",
+ VectorParams.newBuilder()
+ .setSize(4)
+ .setDistance(Distance.Cosine)
+ .build()).get();
+
+client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(1))
+ .setVectors(vectors(0.1f, 0.2f, 0.3f, 0.4f))
+ .build()
+)).get();
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/python.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/python.md
new file mode 100644
index 000000000..3d14576eb
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/python.md
@@ -0,0 +1,13 @@
+```python
+client = QdrantClient(url="https://localhost:6333", api_key="my-admin-key")
+
+client.create_collection(
+ collection_name="my_collection",
+ vectors_config=models.VectorParams(size=4, distance=models.Distance.COSINE),
+)
+
+client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=1, vector=[0.1, 0.2, 0.3, 0.4])],
+)
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/rust.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/rust.md
new file mode 100644
index 000000000..44077026d
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/rust.md
@@ -0,0 +1,19 @@
+```rust
+let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("my-admin-key")
+ .build()?;
+
+client
+ .create_collection(
+ CreateCollectionBuilder::new("my_collection")
+ .vectors_config(VectorParamsBuilder::new(4, Distance::Cosine)),
+ )
+ .await?;
+
+client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(1, vec![0.1_f32, 0.2, 0.3, 0.4], [("source", "tutorial".into())])],
+ ))
+ .await?;
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/typescript.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/typescript.md
new file mode 100644
index 000000000..5e35d5cf7
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-admin-key/typescript.md
@@ -0,0 +1,11 @@
+```typescript
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "my-admin-key" });
+
+await client.createCollection("my_collection", {
+ vectors: { size: 4, distance: "Cosine" },
+});
+
+await client.upsert("my_collection", {
+ points: [{ id: 1, vector: [0.1, 0.2, 0.3, 0.4] }],
+});
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/csharp.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/csharp.md
new file mode 100644
index 000000000..fc6a3a436
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/csharp.md
@@ -0,0 +1,18 @@
+```csharp
+client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "");
+
+try
+{
+ await client.UpsertAsync(
+ collectionName: "other_collection",
+ points: new List
+ {
+ new() { Id = 2, Vectors = new[] { 0.5f, 0.6f, 0.7f, 0.8f } }
+ }
+ );
+}
+catch (Exception e)
+{
+ Console.WriteLine(e.Message); // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/go.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/go.md
new file mode 100644
index 000000000..860b0eb21
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/go.md
@@ -0,0 +1,24 @@
+```go
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "",
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+_, err = client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "other_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(2),
+ Vectors: qdrant.NewVectors(0.5, 0.6, 0.7, 0.8),
+ },
+ },
+})
+if err != nil {
+ fmt.Println(err) // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/java.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/java.md
new file mode 100644
index 000000000..08fd7c926
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/java.md
@@ -0,0 +1,17 @@
+```java
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("")
+ .build());
+
+try {
+ client.upsertAsync("other_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(2))
+ .setVectors(vectors(0.5f, 0.6f, 0.7f, 0.8f))
+ .build()
+ )).get();
+} catch (Exception e) {
+ System.out.println(e.getMessage()); // PERMISSION_DENIED
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/python.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/python.md
new file mode 100644
index 000000000..535652528
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/python.md
@@ -0,0 +1,11 @@
+```python
+client = QdrantClient(url="https://localhost:6333", api_key="")
+
+try:
+ client.upsert(
+ collection_name="other_collection",
+ points=[models.PointStruct(id=2, vector=[0.5, 0.6, 0.7, 0.8])],
+ )
+except Exception as e:
+ print(e) # 403 Forbidden
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/rust.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/rust.md
new file mode 100644
index 000000000..86dc235e1
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/rust.md
@@ -0,0 +1,15 @@
+```rust
+let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("")
+ .build()?;
+
+let result = client
+ .upsert_points(UpsertPointsBuilder::new(
+ "other_collection",
+ vec![PointStruct::new(2, vec![0.5_f32, 0.6, 0.7, 0.8], [("source", "tutorial".into())])],
+ ))
+ .await;
+if let Err(e) = result {
+ println!("{}", e); // PermissionDenied
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/typescript.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/typescript.md
new file mode 100644
index 000000000..480f5b57f
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-ro-collection/typescript.md
@@ -0,0 +1,11 @@
+```typescript
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "" });
+
+try {
+ await client.upsert("other_collection", {
+ points: [{ id: 2, vector: [0.5, 0.6, 0.7, 0.8] }],
+ });
+} catch (e: any) {
+ console.error(e.message); // 403 Forbidden
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/csharp.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/csharp.md
new file mode 100644
index 000000000..20e9a4d4c
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/csharp.md
@@ -0,0 +1,11 @@
+```csharp
+client = new QdrantClient(host: "localhost", port: 6334, https: true, apiKey: "");
+
+await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 2, Vectors = new[] { 0.5f, 0.6f, 0.7f, 0.8f } }
+ }
+);
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/go.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/go.md
new file mode 100644
index 000000000..bb9e28312
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/go.md
@@ -0,0 +1,21 @@
+```go
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "",
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(2),
+ Vectors: qdrant.NewVectors(0.5, 0.6, 0.7, 0.8),
+ },
+ },
+})
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/java.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/java.md
new file mode 100644
index 000000000..4fd895bf8
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/java.md
@@ -0,0 +1,13 @@
+```java
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("")
+ .build());
+
+client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(2))
+ .setVectors(vectors(0.5f, 0.6f, 0.7f, 0.8f))
+ .build()
+)).get();
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/python.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/python.md
new file mode 100644
index 000000000..961f74340
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/python.md
@@ -0,0 +1,8 @@
+```python
+client = QdrantClient(url="https://localhost:6333", api_key="")
+
+client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=2, vector=[0.5, 0.6, 0.7, 0.8])],
+)
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/rust.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/rust.md
new file mode 100644
index 000000000..2198e6508
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/rust.md
@@ -0,0 +1,12 @@
+```rust
+let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("")
+ .build()?;
+
+client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(2, vec![0.5_f32, 0.6, 0.7, 0.8], [("source", "tutorial".into())])],
+ ))
+ .await?;
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/typescript.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/typescript.md
new file mode 100644
index 000000000..459535065
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-jwt-rw-collection/typescript.md
@@ -0,0 +1,7 @@
+```typescript
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "" });
+
+await client.upsert("my_collection", {
+ points: [{ id: 2, vector: [0.5, 0.6, 0.7, 0.8] }],
+});
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/csharp.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/csharp.md
new file mode 100644
index 000000000..a18a1cb76
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/csharp.md
@@ -0,0 +1,26 @@
+```csharp
+using Qdrant.Client;
+using Qdrant.Client.Grpc;
+
+var client = new QdrantClient(host: "localhost", port: 6334, https: true);
+
+try
+{
+ await client.CreateCollectionAsync(
+ collectionName: "my_collection",
+ vectorsConfig: new VectorParams { Size = 4, Distance = Distance.Cosine }
+ );
+
+ await client.UpsertAsync(
+ collectionName: "my_collection",
+ points: new List
+ {
+ new() { Id = 1, Vectors = new[] { 0.1f, 0.2f, 0.3f, 0.4f } }
+ }
+ );
+}
+catch (Exception e)
+{
+ Console.WriteLine(e.Message); // Unauthenticated
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/go.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/go.md
new file mode 100644
index 000000000..5f7530e79
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/go.md
@@ -0,0 +1,38 @@
+```go
+import (
+ "context"
+ "fmt"
+
+ "github.com/qdrant/go-client/qdrant"
+)
+
+client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ UseTLS: true,
+})
+if err != nil {
+ panic(err)
+}
+
+client.CreateCollection(context.Background(), &qdrant.CreateCollection{
+ CollectionName: "my_collection",
+ VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
+ Size: 4,
+ Distance: qdrant.Distance_Cosine,
+ }),
+})
+
+_, err = client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(1),
+ Vectors: qdrant.NewVectors(0.1, 0.2, 0.3, 0.4),
+ },
+ },
+})
+if err != nil {
+ fmt.Println(err) // Unauthenticated
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/java.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/java.md
new file mode 100644
index 000000000..86c89e15e
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/java.md
@@ -0,0 +1,31 @@
+```java
+import static io.qdrant.client.PointIdFactory.id;
+import static io.qdrant.client.VectorsFactory.vectors;
+
+import io.qdrant.client.QdrantClient;
+import io.qdrant.client.QdrantGrpcClient;
+import io.qdrant.client.grpc.Collections.Distance;
+import io.qdrant.client.grpc.Collections.VectorParams;
+import io.qdrant.client.grpc.Points.PointStruct;
+import java.util.List;
+
+client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true).build());
+
+try {
+ client.createCollectionAsync("my_collection",
+ VectorParams.newBuilder()
+ .setSize(4)
+ .setDistance(Distance.Cosine)
+ .build()).get();
+
+ client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(1))
+ .setVectors(vectors(0.1f, 0.2f, 0.3f, 0.4f))
+ .build()
+ )).get();
+} catch (Exception e) {
+ System.out.println(e.getMessage()); // UNAUTHENTICATED
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/python.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/python.md
new file mode 100644
index 000000000..1df0422b7
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/python.md
@@ -0,0 +1,18 @@
+```python
+from qdrant_client import QdrantClient, models
+
+client = QdrantClient(url="https://localhost:6333")
+
+try:
+ client.create_collection(
+ collection_name="my_collection",
+ vectors_config=models.VectorParams(size=4, distance=models.Distance.COSINE),
+ )
+
+ client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=1, vector=[0.1, 0.2, 0.3, 0.4])],
+ )
+except Exception as e:
+ print(e) # 401 Unauthorized
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/rust.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/rust.md
new file mode 100644
index 000000000..55e44049b
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/rust.md
@@ -0,0 +1,23 @@
+```rust
+let client = Qdrant::from_url("https://localhost:6334").build()?;
+
+let result = client
+ .create_collection(
+ CreateCollectionBuilder::new("my_collection")
+ .vectors_config(VectorParamsBuilder::new(4, Distance::Cosine)),
+ )
+ .await;
+if let Err(e) = result {
+ println!("{}", e); // Unauthorized
+}
+
+let result = client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(1, vec![0.1_f32, 0.2, 0.3, 0.4], [("source", "tutorial".into())])],
+ ))
+ .await;
+if let Err(e) = result {
+ println!("{}", e); // Unauthorized
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/typescript.md b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/typescript.md
new file mode 100644
index 000000000..cd7167cf8
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/generated/upsert-no-auth/typescript.md
@@ -0,0 +1,17 @@
+```typescript
+import { QdrantClient } from "@qdrant/js-client-rest";
+
+client = new QdrantClient({ url: "https://localhost:6333" });
+
+try {
+ await client.createCollection("my_collection", {
+ vectors: { size: 4, distance: "Cosine" },
+ });
+
+ await client.upsert("my_collection", {
+ points: [{ id: 1, vector: [0.1, 0.2, 0.3, 0.4] }],
+ });
+} catch (e: any) {
+ console.error(e.message); // 401 Unauthorized
+}
+```
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/go.go b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/go.go
new file mode 100644
index 000000000..54380949f
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/go.go
@@ -0,0 +1,143 @@
+package snippet
+// @block-start upsert-no-auth
+import (
+ "context"
+ "fmt"
+
+ "github.com/qdrant/go-client/qdrant"
+)
+
+func Main() {
+ // @hide-start
+ var client *qdrant.Client
+ var err error
+ // @hide-end
+
+ client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ UseTLS: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ client.CreateCollection(context.Background(), &qdrant.CreateCollection{
+ CollectionName: "my_collection",
+ VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
+ Size: 4,
+ Distance: qdrant.Distance_Cosine,
+ }),
+ })
+
+ _, err = client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(1),
+ Vectors: qdrant.NewVectors(0.1, 0.2, 0.3, 0.4),
+ },
+ },
+ })
+ if err != nil {
+ fmt.Println(err) // Unauthenticated
+ }
+ // @block-end upsert-no-auth
+
+ // @block-start upsert-admin-key
+ client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "my-admin-key",
+ UseTLS: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ client.CreateCollection(context.Background(), &qdrant.CreateCollection{
+ CollectionName: "my_collection",
+ VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
+ Size: 4,
+ Distance: qdrant.Distance_Cosine,
+ }),
+ })
+
+ client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(1),
+ Vectors: qdrant.NewVectors(0.1, 0.2, 0.3, 0.4),
+ },
+ },
+ })
+ // @block-end upsert-admin-key
+
+ // @block-start delete-read-only-key
+ client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "my-read-only-key",
+ UseTLS: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = client.Delete(context.Background(), &qdrant.DeletePoints{
+ CollectionName: "my_collection",
+ Points: qdrant.NewPointsSelector(qdrant.NewIDNum(1)),
+ })
+ if err != nil {
+ fmt.Println(err) // PermissionDenied
+ }
+ // @block-end delete-read-only-key
+
+ // @block-start upsert-jwt-rw-collection
+ client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "",
+ UseTLS: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "my_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(2),
+ Vectors: qdrant.NewVectors(0.5, 0.6, 0.7, 0.8),
+ },
+ },
+ })
+ // @block-end upsert-jwt-rw-collection
+
+ // @block-start upsert-jwt-ro-collection
+ client, err = qdrant.NewClient(&qdrant.Config{
+ Host: "localhost",
+ Port: 6334,
+ APIKey: "",
+ UseTLS: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = client.Upsert(context.Background(), &qdrant.UpsertPoints{
+ CollectionName: "other_collection",
+ Points: []*qdrant.PointStruct{
+ {
+ Id: qdrant.NewIDNum(2),
+ Vectors: qdrant.NewVectors(0.5, 0.6, 0.7, 0.8),
+ },
+ },
+ })
+ if err != nil {
+ fmt.Println(err) // PermissionDenied
+ }
+ // @block-end upsert-jwt-ro-collection
+}
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/java.java b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/java.java
new file mode 100644
index 000000000..77627b079
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/java.java
@@ -0,0 +1,107 @@
+package com.example.snippets_amalgamation;
+
+// @block-start upsert-no-auth
+import static io.qdrant.client.PointIdFactory.id;
+import static io.qdrant.client.VectorsFactory.vectors;
+
+import io.qdrant.client.QdrantClient;
+import io.qdrant.client.QdrantGrpcClient;
+import io.qdrant.client.grpc.Collections.Distance;
+import io.qdrant.client.grpc.Collections.VectorParams;
+import io.qdrant.client.grpc.Points.PointStruct;
+import java.util.List;
+
+public class Snippet {
+
+ public static void run() throws Exception {
+ // @hide-start
+ QdrantClient client = null;
+ // @hide-end
+
+ client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true).build());
+
+ try {
+ client.createCollectionAsync("my_collection",
+ VectorParams.newBuilder()
+ .setSize(4)
+ .setDistance(Distance.Cosine)
+ .build()).get();
+
+ client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(1))
+ .setVectors(vectors(0.1f, 0.2f, 0.3f, 0.4f))
+ .build()
+ )).get();
+ } catch (Exception e) {
+ System.out.println(e.getMessage()); // UNAUTHENTICATED
+ }
+ // @block-end upsert-no-auth
+
+ // @block-start upsert-admin-key
+ client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("my-admin-key")
+ .build());
+
+ client.createCollectionAsync("my_collection",
+ VectorParams.newBuilder()
+ .setSize(4)
+ .setDistance(Distance.Cosine)
+ .build()).get();
+
+ client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(1))
+ .setVectors(vectors(0.1f, 0.2f, 0.3f, 0.4f))
+ .build()
+ )).get();
+ // @block-end upsert-admin-key
+
+ // @block-start delete-read-only-key
+ client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("my-read-only-key")
+ .build());
+
+ try {
+ client.deleteAsync("my_collection", List.of(id(1))).get();
+ } catch (Exception e) {
+ System.out.println(e.getMessage()); // PERMISSION_DENIED
+ }
+ // @block-end delete-read-only-key
+
+ // @block-start upsert-jwt-rw-collection
+ client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("")
+ .build());
+
+ client.upsertAsync("my_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(2))
+ .setVectors(vectors(0.5f, 0.6f, 0.7f, 0.8f))
+ .build()
+ )).get();
+ // @block-end upsert-jwt-rw-collection
+
+ // @block-start upsert-jwt-ro-collection
+ client = new QdrantClient(
+ QdrantGrpcClient.newBuilder("localhost", 6334, true)
+ .withApiKey("")
+ .build());
+
+ try {
+ client.upsertAsync("other_collection", List.of(
+ PointStruct.newBuilder()
+ .setId(id(2))
+ .setVectors(vectors(0.5f, 0.6f, 0.7f, 0.8f))
+ .build()
+ )).get();
+ } catch (Exception e) {
+ System.out.println(e.getMessage()); // PERMISSION_DENIED
+ }
+ // @block-end upsert-jwt-ro-collection
+ }
+}
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/python.py b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/python.py
new file mode 100644
index 000000000..a7027367c
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/python.py
@@ -0,0 +1,65 @@
+# @block-start upsert-no-auth
+from qdrant_client import QdrantClient, models
+
+client = QdrantClient(url="https://localhost:6333")
+
+try:
+ client.create_collection(
+ collection_name="my_collection",
+ vectors_config=models.VectorParams(size=4, distance=models.Distance.COSINE),
+ )
+
+ client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=1, vector=[0.1, 0.2, 0.3, 0.4])],
+ )
+except Exception as e:
+ print(e) # 401 Unauthorized
+# @block-end upsert-no-auth
+
+# @block-start upsert-admin-key
+client = QdrantClient(url="https://localhost:6333", api_key="my-admin-key")
+
+client.create_collection(
+ collection_name="my_collection",
+ vectors_config=models.VectorParams(size=4, distance=models.Distance.COSINE),
+)
+
+client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=1, vector=[0.1, 0.2, 0.3, 0.4])],
+)
+# @block-end upsert-admin-key
+
+# @block-start delete-read-only-key
+client = QdrantClient(url="https://localhost:6333", api_key="my-read-only-key")
+
+try:
+ client.delete(
+ collection_name="my_collection",
+ points_selector=models.PointIdsList(points=[1]),
+ )
+except Exception as e:
+ print(e) # 403 Forbidden
+# @block-end delete-read-only-key
+
+# @block-start upsert-jwt-rw-collection
+client = QdrantClient(url="https://localhost:6333", api_key="")
+
+client.upsert(
+ collection_name="my_collection",
+ points=[models.PointStruct(id=2, vector=[0.5, 0.6, 0.7, 0.8])],
+)
+# @block-end upsert-jwt-rw-collection
+
+# @block-start upsert-jwt-ro-collection
+client = QdrantClient(url="https://localhost:6333", api_key="")
+
+try:
+ client.upsert(
+ collection_name="other_collection",
+ points=[models.PointStruct(id=2, vector=[0.5, 0.6, 0.7, 0.8])],
+ )
+except Exception as e:
+ print(e) # 403 Forbidden
+# @block-end upsert-jwt-ro-collection
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/rust.rs b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/rust.rs
new file mode 100644
index 000000000..e0de2a26a
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/rust.rs
@@ -0,0 +1,99 @@
+use qdrant_client::qdrant::{
+ CreateCollectionBuilder, DeletePointsBuilder, Distance, PointStruct, PointsIdsList,
+ UpsertPointsBuilder, VectorParamsBuilder,
+};
+use qdrant_client::Qdrant;
+
+pub async fn main() -> anyhow::Result<()> {
+ // @block-start upsert-no-auth
+ let client = Qdrant::from_url("https://localhost:6334").build()?;
+
+ let result = client
+ .create_collection(
+ CreateCollectionBuilder::new("my_collection")
+ .vectors_config(VectorParamsBuilder::new(4, Distance::Cosine)),
+ )
+ .await;
+ if let Err(e) = result {
+ println!("{}", e); // Unauthorized
+ }
+
+ let result = client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(1, vec![0.1_f32, 0.2, 0.3, 0.4], [("source", "tutorial".into())])],
+ ))
+ .await;
+ if let Err(e) = result {
+ println!("{}", e); // Unauthorized
+ }
+ // @block-end upsert-no-auth
+
+ // @block-start upsert-admin-key
+ let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("my-admin-key")
+ .build()?;
+
+ client
+ .create_collection(
+ CreateCollectionBuilder::new("my_collection")
+ .vectors_config(VectorParamsBuilder::new(4, Distance::Cosine)),
+ )
+ .await?;
+
+ client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(1, vec![0.1_f32, 0.2, 0.3, 0.4], [("source", "tutorial".into())])],
+ ))
+ .await?;
+ // @block-end upsert-admin-key
+
+ // @block-start delete-read-only-key
+ let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("my-read-only-key")
+ .build()?;
+
+ let result = client
+ .delete_points(
+ DeletePointsBuilder::new("my_collection").points(PointsIdsList {
+ ids: vec![1.into()],
+ }),
+ )
+ .await;
+ if let Err(e) = result {
+ println!("{}", e); // PermissionDenied
+ }
+ // @block-end delete-read-only-key
+
+ // @block-start upsert-jwt-rw-collection
+ let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("")
+ .build()?;
+
+ client
+ .upsert_points(UpsertPointsBuilder::new(
+ "my_collection",
+ vec![PointStruct::new(2, vec![0.5_f32, 0.6, 0.7, 0.8], [("source", "tutorial".into())])],
+ ))
+ .await?;
+ // @block-end upsert-jwt-rw-collection
+
+ // @block-start upsert-jwt-ro-collection
+ let client = Qdrant::from_url("https://localhost:6334")
+ .api_key("")
+ .build()?;
+
+ let result = client
+ .upsert_points(UpsertPointsBuilder::new(
+ "other_collection",
+ vec![PointStruct::new(2, vec![0.5_f32, 0.6, 0.7, 0.8], [("source", "tutorial".into())])],
+ ))
+ .await;
+ if let Err(e) = result {
+ println!("{}", e); // PermissionDenied
+ }
+ // @block-end upsert-jwt-ro-collection
+
+ Ok(())
+}
diff --git a/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/typescript.ts b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/typescript.ts
new file mode 100644
index 000000000..b037eb03d
--- /dev/null
+++ b/qdrant-landing/content/documentation/headless/snippets/tutorial-secure-qdrant/typescript.ts
@@ -0,0 +1,63 @@
+// @block-start upsert-no-auth
+import { QdrantClient } from "@qdrant/js-client-rest";
+
+// @hide-start
+let client: QdrantClient;
+// @hide-end
+
+client = new QdrantClient({ url: "https://localhost:6333" });
+
+try {
+ await client.createCollection("my_collection", {
+ vectors: { size: 4, distance: "Cosine" },
+ });
+
+ await client.upsert("my_collection", {
+ points: [{ id: 1, vector: [0.1, 0.2, 0.3, 0.4] }],
+ });
+} catch (e: any) {
+ console.error(e.message); // 401 Unauthorized
+}
+// @block-end upsert-no-auth
+
+// @block-start upsert-admin-key
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "my-admin-key" });
+
+await client.createCollection("my_collection", {
+ vectors: { size: 4, distance: "Cosine" },
+});
+
+await client.upsert("my_collection", {
+ points: [{ id: 1, vector: [0.1, 0.2, 0.3, 0.4] }],
+});
+// @block-end upsert-admin-key
+
+// @block-start delete-read-only-key
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "my-read-only-key" });
+
+try {
+ await client.delete("my_collection", { points: [1] });
+} catch (e: any) {
+ console.error(e.message); // 403 Forbidden
+}
+// @block-end delete-read-only-key
+
+// @block-start upsert-jwt-rw-collection
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "" });
+
+await client.upsert("my_collection", {
+ points: [{ id: 2, vector: [0.5, 0.6, 0.7, 0.8] }],
+});
+// @block-end upsert-jwt-rw-collection
+
+// @block-start upsert-jwt-ro-collection
+client = new QdrantClient({ url: "https://localhost:6333", apiKey: "" });
+
+try {
+ await client.upsert("other_collection", {
+ points: [{ id: 2, vector: [0.5, 0.6, 0.7, 0.8] }],
+ });
+} catch (e: any) {
+ console.error(e.message); // 403 Forbidden
+}
+// @block-end upsert-jwt-ro-collection
diff --git a/qdrant-landing/content/documentation/ops-configuration/configuration.md b/qdrant-landing/content/documentation/ops-configuration/configuration.md
index c75be02e6..8fd6629f0 100644
--- a/qdrant-landing/content/documentation/ops-configuration/configuration.md
+++ b/qdrant-landing/content/documentation/ops-configuration/configuration.md
@@ -77,7 +77,7 @@ Environment variables follow this format: they should be prefixed with `QDRANT__
docker run -p 6333:6333 \
-e QDRANT__LOG_LEVEL=INFO \
-e QDRANT__SERVICE__API_KEY= \
- -e QDRANT__SERVICE__ENABLE_TLS=1 \
+ -e QDRANT__SERVICE__ENABLE_TLS=true \
-e QDRANT__TLS__CERT=./tls/cert.pem \
qdrant/qdrant
```
diff --git a/qdrant-landing/content/documentation/security.md b/qdrant-landing/content/documentation/security.md
index 51bb1e776..4fe565dbe 100644
--- a/qdrant-landing/content/documentation/security.md
+++ b/qdrant-landing/content/documentation/security.md
@@ -13,6 +13,8 @@ aliases:
Securing a Qdrant deployment means controlling who can access your data, encrypting traffic, and keeping an audit trail for compliance. To secure your deployments, Qdrant supports [API key authentication](#authentication) (including [read-only API keys](#read-only-api-key) for query-only consumers and [granular access API keys](#granular-access-api-keys) with per-collection read/write scoping), [network binding](#network-bind), [TLS](#tls) for encrypted connections, and [audit logging](#audit-logging) for compliance. On Qdrant Cloud, these features are enabled by default. On self-hosted open source deployments, they must be explicitly configured before going to production.
+> For a hands-on tutorial on securing a self-hosted Qdrant instance, see [Secure a Self-Hosted Qdrant Instance](/documentation/tutorials-operations/secure-qdrant/).
+
## Secure Your Instance
diff --git a/qdrant-landing/content/documentation/tutorials-operations/secure-qdrant.md b/qdrant-landing/content/documentation/tutorials-operations/secure-qdrant.md
new file mode 100644
index 000000000..edbbb2ed2
--- /dev/null
+++ b/qdrant-landing/content/documentation/tutorials-operations/secure-qdrant.md
@@ -0,0 +1,342 @@
+---
+title: Secure a Self-Hosted Qdrant Instance
+short_description: "Harden a self-hosted Qdrant deployment with TLS, an Admin API key, a Read-Only key, and Granular Access Control."
+description: "Tutorial: secure a self-hosted Qdrant instance step by step: enable TLS, set up an Admin API key, restrict consumers with a Read-Only key, and issue collection-scoped JWT tokens."
+weight: 45
+---
+
+# Secure a Self-Hosted Qdrant Instance
+
+| Time: 45 min | Level: Intermediate |
+| --- | ----------- |
+
+Qdrant offers a comprehensive set of [security and access control features](/documentation/security/) that enable you to protect your data and control access at multiple levels. By default, these features are enabled on Qdrant Cloud deployments. However, self-hosted Qdrant deployments default to no authentication and no encryption: every interface on the host is reachable without a key or password. For self-hosted instances, it is crucial to secure your instance before connecting it to any network.
+
+This tutorial walks through securing a self-hosted Qdrant instance step by step. You will:
+
+- **Enable TLS** to encrypt traffic between clients and your Qdrant instance.
+- **Set up an admin API key** to require authentication for all requests.
+- **Restrict consumers with a read-only key** to prevent unintended writes.
+- **Issue granular access API keys** to scope permissions to specific collections.
+
+> Qdrant Cloud deployments are always secure by default. This tutorial covers self-hosted deployments only. While this tutorial uses Docker Compose, the same security features and configurations apply to any self-hosted deployment method.
+
+## Prerequisites
+
+- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed
+- `curl` available in your terminal
+- [mkcert](https://github.com/FiloSottile/mkcert#readme) for generating a local self-signed certificate ([installation instructions](https://github.com/FiloSottile/mkcert#installation))
+- TLS requires Qdrant 1.2 or later, API key authentication requires Qdrant 1.2 or later, and granular access API keys (JWT) require Qdrant 1.9 or later. This tutorial uses the latest Qdrant image, which includes all these features.
+
+---
+
+## Step 1: Start an Unsecured Instance
+
+Start Qdrant using the [standard Docker Compose setup](/documentation/installation/#docker-compose). Create a `docker-compose.yml` file:
+
+```yaml
+services:
+ qdrant:
+ image: qdrant/qdrant
+ ports:
+ - "6333:6333"
+ - "6334:6334"
+ volumes:
+ - qdrant_storage:/qdrant/storage:z
+
+volumes:
+ qdrant_storage:
+```
+
+Start the instance:
+
+```bash
+docker compose up -d
+```
+
+Confirm that no credentials are required when connecting to the REST API port with `curl`:
+
+```bash
+curl http://localhost:6333
+```
+
+Expected response:
+
+```json
+{"title":"qdrant - vector search engine","version":"...","commit":"..."}
+```
+
+
+
+---
+
+## Step 2: Enable TLS
+
+Unencrypted connections allow anyone on the network to read your API key and data in transit. Enable TLS to encrypt all traffic.
+
+First, add a local certificate authority to your system trust store, so `curl` and your browser will accept the certificate without extra flags.
+
+```bash
+mkcert -install
+```
+
+Next, generate a locally trusted certificate with mkcert:
+
+```bash
+mkdir tls && mkcert -cert-file tls/cert.pem -key-file tls/key.pem localhost 127.0.0.1
+```
+
+If you're using the Python or TypeScript clients, set the following environment variables to allow the clients to find the certificate:
+
+```python
+export SSL_CERT_FILE=$(mkcert -CAROOT)/rootCA.pem
+```
+```typescript
+export NODE_EXTRA_CA_CERTS=$(mkcert -CAROOT)/rootCA.pem
+```
+
+If you're using the Java client, add the certificate to the Java trust store:
+
+```java
+keytool -importcert \
+ -file $(mkcert -CAROOT)/rootCA.pem \
+ -alias mkcert-local \
+ -keystore $JAVA_HOME/lib/security/cacerts \
+ -storepass changeit -noprompt
+
+```
+
+Next, update `docker-compose.yml` to enable TLS and mount the certificate files:
+
+```yaml
+services:
+ qdrant:
+ image: qdrant/qdrant
+ ports:
+ - "6333:6333"
+ - "6334:6334"
+ environment:
+ QDRANT__SERVICE__ENABLE_TLS: "true"
+ QDRANT__TLS__CERT: /qdrant/tls/cert.pem
+ QDRANT__TLS__KEY: /qdrant/tls/key.pem
+ volumes:
+ - ./tls:/qdrant/tls:ro
+ - qdrant_storage:/qdrant/storage:z
+
+volumes:
+ qdrant_storage:
+```
+
+Restart Qdrant to apply the changes:
+
+```bash
+docker compose down && docker compose up -d
+```
+
+Now, unencrypted HTTP requests are rejected:
+
+```bash
+curl http://localhost:6333
+```
+
+However, HTTPS requests succeed:
+
+```bash
+curl https://localhost:6333
+```
+
+Refer to [Security > TLS](/documentation/security/#tls) to learn more about TLS configuration.
+
+---
+
+## Step 3: Enable an Admin API Key
+
+Without enabling authentication, anyone with network access to a Qdrant instance can read, write, or delete all its data. Set an [admin API key](/documentation/security/#authentication) to require credentials on every request.
+
+Set the `QDRANT__SERVICE__API_KEY` environment variable to the API key in `docker-compose.yml`:
+
+```yaml
+ environment:
+ QDRANT__SERVICE__ENABLE_TLS: "true"
+ QDRANT__TLS__CERT: /qdrant/tls/cert.pem
+ QDRANT__TLS__KEY: /qdrant/tls/key.pem
+ QDRANT__SERVICE__API_KEY: "my-admin-key"
+```
+
+Restart Qdrant to apply the changes:
+
+```bash
+docker compose down && docker compose up -d
+```
+
+Verify that unauthenticated requests are now rejected:
+
+```bash
+curl https://localhost:6333/collections
+```
+
+The same behavior applies to the clients. Ingesting a point without an API key is blocked:
+
+{{< code-snippet path="/documentation/headless/snippets/tutorial-secure-qdrant/" block="upsert-no-auth" >}}
+
+With the admin API key, the request succeeds:
+
+```bash
+curl -X PUT 'https://localhost:6333/collections/my_collection' \
+ -H 'Content-Type: application/json' \
+ -H 'api-key: my-admin-key' \
+ -d '{
+ "vectors": {
+ "size": 4,
+ "distance": "Cosine"
+ }
+ }'
+
+curl -X PUT 'https://localhost:6333/collections/my_collection/points' \
+ -H 'Content-Type: application/json' \
+ -H 'api-key: my-admin-key' \
+ -d '{
+ "points": [
+ {"id": 1, "vector": [0.1, 0.2, 0.3, 0.4]}
+ ]
+ }'
+```
+
+{{< code-snippet path="/documentation/headless/snippets/tutorial-secure-qdrant/" block="upsert-admin-key" >}}
+
+Refer to [Security > Authentication](/documentation/security/#authentication) to learn more about admin API keys, including API key rotation.
+
+---
+
+## Step 4: Enable a Read-Only API Key
+
+Issue a separate [read-only API key](/documentation/security/#read-only-api-key) for services that only need to read data. With this key, a client application can search and read but cannot upsert, delete, or modify data.
+
+Set the `QDRANT__SERVICE__READ_ONLY_API_KEY` environment variable to the read-only key in `docker-compose.yml`:
+
+```yaml
+ environment:
+ QDRANT__SERVICE__ENABLE_TLS: "true"
+ QDRANT__TLS__CERT: /qdrant/tls/cert.pem
+ QDRANT__TLS__KEY: /qdrant/tls/key.pem
+ QDRANT__SERVICE__API_KEY: "my-admin-key"
+ QDRANT__SERVICE__READ_ONLY_API_KEY: "my-read-only-key"
+```
+
+Restart Qdrant:
+
+```bash
+docker compose down && docker compose up -d
+```
+
+Verify that a delete attempt with the read-only key is rejected:
+
+```bash
+curl -X POST https://localhost:6333/collections/my_collection/points/delete \
+ -H "api-key: my-read-only-key" \
+ -H "Content-Type: application/json" \
+ -d '{"points": [1]}'
+```
+
+Or with a client:
+
+{{< code-snippet path="/documentation/headless/snippets/tutorial-secure-qdrant/" block="delete-read-only-key" >}}
+
+Reads still succeed with the read-only key:
+
+```bash
+curl https://localhost:6333/collections/my_collection \
+ -H "api-key: my-read-only-key"
+```
+
+Both keys can be used simultaneously. See [Security > Read-Only API Key](/documentation/security/#read-only-api-key).
+
+---
+
+## Step 5: Set Up Granular Access API Keys (JWT)
+
+The admin and read-only keys apply globally. For finer control, use [granular access API Keys](/documentation/security/#granular-access-api-keys) (JSON Web Tokens, JWT). For example, you can use JWT to provide read-write access to one collection and read-only access to another.
+
+Enable JWT RBAC in `docker-compose.yml`:
+
+```yaml
+ environment:
+ QDRANT__SERVICE__ENABLE_TLS: "true"
+ QDRANT__TLS__CERT: /qdrant/tls/cert.pem
+ QDRANT__TLS__KEY: /qdrant/tls/key.pem
+ QDRANT__SERVICE__API_KEY: "my-admin-key"
+ QDRANT__SERVICE__READ_ONLY_API_KEY: "my-read-only-key"
+ QDRANT__SERVICE__JWT_RBAC: "true"
+```
+
+Restart:
+
+```bash
+docker compose down && docker compose up -d
+```
+
+Create a second collection `other_collection` using the admin API key:
+
+```bash
+curl -X PUT https://localhost:6333/collections/other_collection \
+ -H "api-key: my-admin-key" \
+ -H "Content-Type: application/json" \
+ -d '{"vectors": {"size": 4, "distance": "Cosine"}}'
+```
+
+Generate a JWT in the Web UI:
+
+1. Open `https://localhost:6333/dashboard#/jwt`.
+
+ If you get a warning about the connection not being private, this is because the certificate is self-signed. If so, restart the browser, and it should recognize the certificate as trusted.
+1. Select **Collection Access**.
+1. For `my_collection`, select **Read** and **Write**.
+1. For `other_collection`, select **Read** only.
+1. Copy the generated JWT Token.
+
+
+
+
+ Generating a JWT token with the desired access levels using the Web UI.
+
+
+
+> JWT tokens can also be generated programmatically. See [Security > Granular Access API Keys](/documentation/security/#granular-access-api-keys) for a list of libraries that can be used to generate JWT tokens.
+
+Using the JWT token, writing to `my_collection` (`rw` scope) should succeed:
+
+```bash
+curl -X PUT https://localhost:6333/collections/my_collection/points \
+ -H "api-key: " \
+ -H "Content-Type: application/json" \
+ -d '{"points": [{"id": 2, "vector": [0.5, 0.6, 0.7, 0.8]}]}'
+```
+
+With a client too:
+
+{{< code-snippet path="/documentation/headless/snippets/tutorial-secure-qdrant/" block="upsert-jwt-rw-collection" >}}
+
+However, writing to `other_collection` (`r` scope) is blocked:
+
+```bash
+curl -X PUT https://localhost:6333/collections/other_collection/points \
+ -H "api-key: " \
+ -H "Content-Type: application/json" \
+ -d '{"points": [{"id": 2, "vector": [0.5, 0.6, 0.7, 0.8]}]}'
+```
+
+With a client too:
+
+{{< code-snippet path="/documentation/headless/snippets/tutorial-secure-qdrant/" block="upsert-jwt-ro-collection" >}}
+
+See [Security > Granular Access Control with JWT](/documentation/security/#granular-access-api-keys) for the full list of available JWT claims and the complete access-level table.
+
+---
+
+## What's Next
+
+Your instance now has TLS encryption, API key authentication, a read-only key for query consumers, and collection-scoped JWT tokens. For production deployments, also consider:
+
+- [Network Bind](/documentation/security/#network-bind) — restrict which network interfaces Qdrant listens on.
+- [API Key Rotation](/documentation/security/#rotate-an-admin-api-key) — rotate admin API keys in a distributed deployment without downtime.
+- [Production Checklist](/documentation/production-checklist/) — a full checklist of security and reliability settings for production.
diff --git a/qdrant-landing/static/documentation/tutorials/secure-qdrant/generate-jwt.png b/qdrant-landing/static/documentation/tutorials/secure-qdrant/generate-jwt.png
new file mode 100644
index 000000000..90aa1e9c9
Binary files /dev/null and b/qdrant-landing/static/documentation/tutorials/secure-qdrant/generate-jwt.png differ