Pinecone Free code examples in C#, Python, Java and JavaScript
Managed vector database — no cluster to operate.
WHAT THIS PAGE COVERS
Namespaces and metadata filters, which is how multi-tenant RAG stays correct.
text-embedding-3-small
pinecone
Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.
THE OFFICIAL PACKAGE IS Pinecone.Client. `Pinecone.NET` is a community library with a similar name and a different API — methods without the Async suffix, MetadataMap instead of Metadata, and an environment argument that no longer exists.
dotnet add package Pinecone.Client
setx PINECONE_API_KEY "pcsk_your-key" # Windows, then open a new shell
export PINECONE_API_KEY="pcsk_your-key" # macOS / Linux
The client is cheap; the index handle is what you hold. DescribeIndexStatsAsync is the fastest way to confirm the key, the index name and the dimension all line up.
using Pinecone;
var apiKey = Environment.GetEnvironmentVariable("PINECONE_API_KEY")
?? throw new InvalidOperationException("PINECONE_API_KEY is not set.");
var pinecone = new PineconeClient(apiKey);
var index = pinecone.Index("documents");
var stats = await index.DescribeIndexStatsAsync(new DescribeIndexStatsRequest());
// Expect 1536 here if the index was built for text-embedding-3-small.
Console.WriteLine($"dimension {stats.Dimension}, {stats.TotalVectorCount} vectors");
foreach (var (name, summary) in stats.Namespaces ?? [])
{
Console.WriteLine($" {name}: {summary.VectorCount}");
}
Namespace is a field on the request, not a separate client. Metadata values are typed, so a page number stays a number and stays filterable as one.
var vectors = new List<Vector>
{
new()
{
Id = "doc-1#chunk-0",
Values = embedding, // float[] of length 1536
Metadata = new Metadata
{
["doc_id"] = "doc-1",
["source"] = "handbook.pdf",
["page"] = 4,
["text"] = "The first eighty characters or so, for display.",
},
},
};
await index.UpsertAsync(new UpsertRequest
{
Vectors = vectors,
Namespace = "tenant-acme",
});
IncludeMetadata is false by default. The namespace is what keeps one customer's query away from another's data — a tenant id in the filter is one forgotten clause from a breach.
var response = await index.QueryAsync(new QueryRequest
{
Namespace = "tenant-acme", // hard partition
Vector = queryVector,
TopK = 5,
Filter = new Metadata
{
["source"] = new Metadata { ["$eq"] = "handbook.pdf" },
},
IncludeMetadata = true, // off by default
});
foreach (var match in response.Matches ?? [])
{
Console.WriteLine($"{match.Score:F3} {match.Metadata?["text"]}");
}
The gRPC client is worth the extra dependency for bulk upserts — the REST one is fine for queries and noticeably slower for ingestion.
pip install "pinecone[grpc]"
export PINECONE_API_KEY="pcsk_your-key" # macOS / Linux
setx PINECONE_API_KEY "pcsk_your-key" # Windows, then open a new shell
THE DIMENSION IS FIXED FOR THE LIFE OF THE INDEX. It must match the embedding model exactly — 1536 for text-embedding-3-small — and changing model later means a new index and a full re-embed.
import os
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
if not pc.has_index("documents"):
pc.create_index(
name="documents",
dimension=1536, # exactly text-embedding-3-small
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1"),
)
index = pc.Index("documents")
The namespace is a partition, not a filter. Nothing in one namespace is visible from another, so a per-tenant namespace makes cross-tenant leakage impossible rather than merely unlikely.
index.upsert(
vectors=[
{
"id": "doc-1#chunk-0",
"values": vector, # 1536 floats
"metadata": {
"doc_id": "doc-1",
"source": "handbook.pdf",
"page": 4,
"text": "The first eighty characters or so, for display.",
},
},
],
namespace="tenant-acme",
)
The namespace scopes the search; the filter narrows it. include_metadata is off by default, and forgetting it returns ids with no way to show the user what was found.
results = index.query(
namespace="tenant-acme", # hard partition
vector=query_vector,
top_k=5,
filter={ # narrowing within the tenant
"source": {"$eq": "handbook.pdf"},
"page": {"$gte": 1},
},
include_metadata=True, # off by default
)
for match in results["matches"]:
print(round(match["score"], 3), match["metadata"]["text"])
Chunk ids prefixed with the document id make deletion a list-and-delete rather than a metadata scan. Getting this wrong means stale chunks that keep being retrieved after the source is gone.
# Ids were written as "doc-1#chunk-0", "doc-1#chunk-1", ...
for page in index.list(prefix="doc-1#", namespace="tenant-acme"):
index.delete(ids=page, namespace="tenant-acme")
# Deleting a whole tenant is one call, because the namespace is real.
index.delete(delete_all=True, namespace="tenant-acme")
The Java client wraps gRPC, so the index connection is a resource with a lifetime — build it once and close it on shutdown.
# Gradle
implementation("io.pinecone:pinecone-client:5.1.0")
export PINECONE_API_KEY="pcsk_your-key"
getIndexConnection holds a gRPC channel. Opening one per request exhausts connections under load — hold it for the life of the process.
import io.pinecone.clients.Index;
import io.pinecone.clients.Pinecone;
public class Search {
public static void main(String[] args) {
String apiKey = System.getenv("PINECONE_API_KEY");
if (apiKey == null) throw new IllegalStateException("PINECONE_API_KEY is not set.");
Pinecone pinecone = new Pinecone.Builder(apiKey).build();
// One connection for the life of the process.
Index index = pinecone.getIndexConnection("documents");
System.out.println(index.describeIndexStats());
}
}
Vectors are built with buildUpsertVectorWithUnsignedIndices — id, values, sparse indices, sparse values, metadata — and the NAMESPACE is the second argument to upsert. Leave it off and everything lands in the default namespace, which then answers every tenant's queries.
import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import io.pinecone.proto.UpsertResponse;
import io.pinecone.unsigned_indices_model.VectorWithUnsignedIndices;
import java.util.Arrays;
import java.util.List;
import static io.pinecone.commons.IndexInterface.buildUpsertVectorWithUnsignedIndices;
Struct metadata = Struct.newBuilder()
.putFields("doc_id", Value.newBuilder().setStringValue("doc-1").build())
.putFields("source", Value.newBuilder().setStringValue("handbook.pdf").build())
.putFields("page", Value.newBuilder().setNumberValue(4).build())
.build();
// 1536 floats — the width of text-embedding-3-small.
List<Float> values = Arrays.asList(embedding);
List<VectorWithUnsignedIndices> vectors = List.of(
buildUpsertVectorWithUnsignedIndices(
"doc-1#chunk-0", values, null, null, metadata));
UpsertResponse response = index.upsert(vectors, "tenant-acme");
queryByVectorId scopes to the namespace, which is the boundary that matters — nothing in one namespace is reachable from another. The vector-plus-filter overload exists too; check its signature in the SDK reference rather than adapting the Python shape by eye.
import io.pinecone.unsigned_indices_model.QueryResponseWithUnsignedIndices;
import io.pinecone.unsigned_indices_model.ScoredVectorWithUnsignedIndices;
QueryResponseWithUnsignedIndices response = index.queryByVectorId(
5, // topK
"doc-1#chunk-0",
"tenant-acme"); // the namespace — the hard partition
for (ScoredVectorWithUnsignedIndices match : response.getMatchesList()) {
System.out.printf("%.3f %s%n", match.getScore(), match.getMetadata());
}
// Deleting a whole tenant is one call, because the namespace is real
// rather than a metadata field every query has to remember.
index.deleteAll("tenant-acme");
The npm package is scoped. The client reads PINECONE_API_KEY if you pass no arguments.
npm install @pinecone-database/pinecone
export PINECONE_API_KEY="pcsk_your-key"
waitUntilReady matters: a serverless index takes a few seconds to come up, and upserting into one that is still initialising fails in a way that reads like a permissions error.
import { Pinecone } from "@pinecone-database/pinecone";
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const existing = await pc.listIndexes();
if (!existing.indexes?.some((index) => index.name === "documents")) {
await pc.createIndex({
name: "documents",
dimension: 1536, // exactly text-embedding-3-small
metric: "cosine",
spec: { serverless: { cloud: "aws", region: "us-east-1" } },
waitUntilReady: true,
});
}
const index = pc.index("documents");
namespace() returns a scoped handle. Holding one per tenant is how the tenant id stops being something every call site has to remember.
const tenant = index.namespace("tenant-acme");
await tenant.upsert([
{
id: "doc-1#chunk-0",
values: vector, // 1536 floats
metadata: {
doc_id: "doc-1",
source: "handbook.pdf",
page: 4,
text: "The first eighty characters or so, for display.",
},
},
]);
The namespace is the security boundary and the filter is the relevance boundary. Conflating the two — a tenant id in metadata — is the bug that leaks documents between customers.
const results = await tenant.query({
vector: queryVector,
topK: 5,
filter: {
source: { $eq: "handbook.pdf" },
page: { $gte: 1 },
},
includeMetadata: true, // off by default
});
for (const match of results.matches) {
console.log(match.score.toFixed(3), match.metadata.text);
}
Prefixing chunk ids with the document id turns deletion into a list. Without it, removing one source document means scanning metadata across the namespace.
// Ids written as "doc-1#chunk-0", "doc-1#chunk-1", ...
const stale = await tenant.listPaginated({ prefix: "doc-1#" });
if (stale.vectors?.length) {
await tenant.deleteMany(stale.vectors.map((vector) => vector.id));
}
// Removing a tenant entirely is one call.
await tenant.deleteAll();
QUESTIONS
Which languages are the Pinecone examples in?
C#, Python, Java and JavaScript. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.
Which package do I install for Pinecone?
The samples use pinecone. The install step on each language tab gives the exact command.
Which Pinecone model do these examples use?
text-embedding-3-small, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.
Where is the official Pinecone documentation?
https://docs.pinecone.io/ — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.
ALSO IN EMBEDDINGS & SEARCH
The authority
Official Pinecone documentation
Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.
https://docs.pinecone.io/