Hanzo AI

Pinecone

Pinecone is a managed vector database with a control plane, a per-index data host and a namespace on every call; here that is /v1/ai (272) for the vectors and the ingest path, /v1/index (17) for the lexical half, and one hostname with the tenant taken from the key.

Pinecone stores vectors and returns the nearest ones to a query. Here that lives inside /v1/ai (272 operations) — stores, vectors, and an ingest path that parses, chunks and embeds in one call — with /v1/index (17) as the lexical half and POST /v1/search fusing both by reciprocal rank. The structural difference is where the tenant comes from: Pinecone partitions with a namespace string passed on every data-plane call, so isolation is a discipline your code keeps, while here the org is minted from the validated key's owner claim and no request body can name another one.

Start here

Ingest a document and search it. There is no index to create first — the store is named on the call, and the org it belongs to comes from the key.

# 1. mint a key — sk- belongs on a server, pk- is safe in a browser
curl -sS -X POST https://api.hanzo.ai/v1/account/keys \
  -H "Authorization: Bearer $HANZO_SESSION" \
  -H 'Content-Type: application/json' \
  -d '{"type":"secret"}'

# 2. ingest — parse, chunk and embed in one call, no embedding step of your own
curl -sS -X POST https://api.hanzo.ai/v1/ai/rag/ingest \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"store":"docs","text":"Hanzo IAM issues OIDC tokens.","metadata":{"src":"handbook"}}'

# 3. search it — semantic and lexical fused by reciprocal rank
curl -sS -X POST https://api.hanzo.ai/v1/search \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"query":"how do tokens get issued","stores":["docs"]}'

Three calls and you have retrieval. Pinecone's equivalent needs an index created on the control plane, its host read back, an embedding model called yourself, and a namespace passed on every data-plane call after that.

Core capabilities

CapabilityWhat it doesOperations
/v1/aiStores, vectors, and the ingest path that parses, chunks and embeds272
/v1/indexThe lexical half — terms, filters and facets17
/v1/knowledgeCollections built on top of both, for an agent to read9

Nouns

The index, and the namespace inside it

/v1/provisioning (28) · /v1/ai (272) · /v1/index (17)

PineconeHanzo
ProjectYour org, taken from the validated key
POST /indexes — name, dimension, metric, cloud, regionPOST /v1/provisioning/vector — a name, and nothing else to decide
GET /indexes, GET /indexes/{name}GET /v1/provisioning/vector, GET /v1/provisioning/vector/{name}
DELETE /indexes/{name}DELETE /v1/provisioning/vector/{name}
The index host, read back before the first writehost on the create receipt, and it is the public gateway — one address for every collection
Namespace, on every data-plane callNot a field. The org is minted from the key; project narrows within it
A store the assistant reads fromPOST /v1/ai/stores, listed at GET /v1/ai/stores
Sparse index, for the keyword half of hybridPOST /v1/index/indexes — a lexical index, written at .../documents

The physical collection name is derived from your slug under an org-namespacing hash, so it is not the slug and two orgs cannot land on one.

Records

/v1/ai (272) · /v1/index (17)

PineconeHanzo
POST /vectors/upsertPOST /v1/ai/stores/{owner}/{name}/vectors, or POST /v1/ai/vectors for one
Integrated records — upsert text, embedded server-sidePOST /v1/ai/rag/ingest — parse, chunk, embed and index in one call
Re-embedding one documentPOST /v1/ai/rag/embed — the same file_id replaces its own chunks
GET /vectors/fetchGET /v1/ai/vectors/{owner}/{name}, GET /v1/index/indexes/{uid}/documents/{id}
GET /vectors/list, paged by id prefixGET /v1/ai/rag/context — every stored chunk of one file_id
Walking the corpusGET /v1/index/indexes/{uid}/documents — insertion order, with the index total
POST /vectors/delete by idDELETE /v1/ai/vectors/{owner}/{name}, POST /v1/index/indexes/{uid}/documents/delete-batch
Deleting everything one document put inPOST /v1/ai/rag/delete, by file_id
POST /bulk/imports from object storagePOST /v1/ai/rag/ingest with an s3, github or crawl source
POST /describe_index_statsGET /v1/index/stats — per-index counts and the org's total

Query, and the inference beside it

/v1/ai (272) · /v1/search (1) · /v1/knowledge (9)

PineconeHanzo
POST /query with a vector and topKPOST /v1/search with text — query, mode, limit
Dense and sparse indexes, merged in your clientPOST /v1/search with mode hybrid — both legs, fused by reciprocal rank on the server
filter such as {"user":{"$eq":"alice"}}filter on POST /v1/index/indexes/{uid}/search, written user = alice
Declaring which metadata is indexedPATCH /v1/index/indexes/{uid}/settingsfilterableAttributes, replaced whole
POST /records/.../search over textPOST /v1/ai/rag/query, or POST /v1/ai/rag/query-multiple over a set of files
Searching a private corpusPOST /v1/knowledge/search — the org's pages, memories and synced sources
Inference POST /embedPOST /v1/embeddingszen-embedding, 8K context
Inference POST /rerankPOST /v1/rerankzen-rerank
GET /modelsGET /v1/models
Assistant — upload files, chat over themPOST /v1/ai/files/upload, then POST /v1/chat/completions
Keeping an index fresh from a sourcePOST /v1/knowledge/connectors/{provider}/sync — github, slack, google, notion
Importing a vault of notesPOST /v1/knowledge/import — obsidian, notion, roam or evernote

The call

Pinecone, from nothing to a first result:

# 1. create the index — dimension and metric are fixed here, permanently
curl -sS -X POST https://api.pinecone.io/indexes \
  -H "Api-Key: $PINECONE_API_KEY" \
  -H 'X-Pinecone-API-Version: 2025-04' \
  -H 'Content-Type: application/json' \
  -d '{"name":"docs","dimension":1024,"metric":"cosine",
       "spec":{"serverless":{"cloud":"aws","region":"us-east-1"}}}'

# 2. poll until ready, and read back the host this index answers on
HOST=$(curl -sS https://api.pinecone.io/indexes/docs \
  -H "Api-Key: $PINECONE_API_KEY" \
  -H 'X-Pinecone-API-Version: 2025-04' | jq -r .host)

# 3. embed on one hostname, write on the other
curl -sS -X POST https://api.pinecone.io/embed \
  -H "Api-Key: $PINECONE_API_KEY" \
  -H 'X-Pinecone-API-Version: 2025-04' \
  -H 'Content-Type: application/json' \
  -d '{"model":"multilingual-e5-large","inputs":[{"text":"the Q3 roadmap"}]}'

curl -sS -X POST "https://$HOST/vectors/upsert" \
  -H "Api-Key: $PINECONE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"namespace":"acme","vectors":[
        {"id":"doc-1","values":[0.02,0.31],"metadata":{"user":"alice"}}]}'

# 4. query, reusing the vector rather than embedding the text again
curl -sS -X POST "https://$HOST/query" \
  -H "Api-Key: $PINECONE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"namespace":"acme","topK":5,"vector":[0.02,0.31],
       "filter":{"user":{"$eq":"alice"}},"includeMetadata":true}'

Hanzo, the same thing:

curl -sS -X POST https://api.hanzo.ai/v1/ai/rag/ingest \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"source":"upload","store":"docs",
       "documents":[{"id":"doc-1","text":"the Q3 roadmap"}]}'

curl -sS -X POST https://api.hanzo.ai/v1/search \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"query":"roadmap","mode":"hybrid","limit":5}'

Five calls across two hostnames become two against one, and it is five only because the query reuses the vector from the upsert instead of embedding the query text again. There is no index host to resolve, because an index is a row rather than an appliance; and no namespace in either body, because the org is minted from the validated bearer's owner claim, so two orgs both holding a docs index have no way to write each other's name down. The write is applied before its response — GET /v1/index/tasks/{uid} reports succeeded on the first poll and isIndexing in GET /v1/index/stats is always false — so the query that follows a write sees it, rather than seeing it soon. Pinecone asks you to keep the index name, the resolved host, the namespace, the record id, the metadata key and the embedding model's output width in step; here it is the store name and the document id.

The metadata filter is the one place that takes two calls, because an attribute has to be declared filterable before a query may constrain it:

curl -sS -X PATCH https://api.hanzo.ai/v1/index/indexes/docs/settings \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"filterableAttributes":["user","conversationId"]}'

curl -sS -X POST https://api.hanzo.ai/v1/index/indexes/docs/search \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"q":"roadmap","filter":"user = alice","limit":20}'

What does not carry

Namespace is not a field, and the one-call namespace drop goes with it. Pinecone puts thousands of end-user partitions in one index and deletes one with a single call. The org here comes from the key, project is the one narrowing coordinate beneath it, and per-end-user scoping is a filterable attribute — a filter, not a partition. Deleting one user's records is a list of keys at POST /v1/index/indexes/{uid}/documents/delete-batch, not a namespace drop.

You cannot query by a vector you computed yourself. Every query endpoint in this document takes text: query on POST /v1/search, q on the index search, query on POST /v1/knowledge/search. If you embed with your own model — images, a fine-tune, anything outside POST /v1/embeddings — the answer is POST /v1/provisioning/vector, which hands back a host, a port and a connection string once, and you speak the backend's own protocol to it.

No sparse values on either side. Pinecone's hybrid is a dense vector and a sparse one you build and send together. Here hybrid is two engines — a lexical index and a semantic one — fused by reciprocal rank at POST /v1/search, which reports each leg's status and timing in backends. That is a different mechanism, not a different spelling: there is nowhere to put a sparse encoding you built.

No dimension and no metric to choose. POST /v1/provisioning/vector takes a name; POST /v1/index/indexes takes a uid and an optional primaryKey. Nothing takes a width or a distance function. The semantic leg is cosine — score on a knowledge hit is documented as cosine similarity from -1 to 1 — and changing embedding model means re-ingesting, because nothing converts vectors of one width into another.

No snapshot and restore. Pinecone backs an index up to a collection or a backup and restores it into a new index. No route here does that. A corpus is rebuilt from its source instead: POST /v1/ai/rag/ingest naming github, crawl or s3, or POST /v1/knowledge/import for an exported vault.

No capacity to size. Pods, replicas, pod_type and configure_index have no equivalent. A create body is a name plus an optional instance binding; a shared-backend create answers ready, and a dedicated one answers 201 while it is still launching and reaches ready only when a later read reconciles it against the operator's live record. Nothing sets throughput.

How is this guide?