Embeddings
Generate Embeddings
POST /v1/embeddings
Generate vector embeddings for text inputs. Compatible with OpenAI’s Embeddings API.
Request
{
"model": "openai/text-embedding-3-large",
"input": ["hello world", "hola mundo"]
}Response
Usage is tracked internally and not returned:
{
"object": "list",
"model": "openai/text-embedding-3-large",
"data": [
{ "object": "embedding", "index": 0, "embedding": [0.1, 0.2, ...] }
]
}Supported Models
openai/text-embedding-3-large- 3072 dimensionsopenai/text-embedding-3-small- 1536 dimensionsopenai/text-embedding-ada-002- 1536 dimensions (legacy)
Shared Embedding Resources
Manage vector embedding resources for semantic search. Resources store items with their embeddings and support similarity queries.
Access Modes
Each resource has an access mode set at creation time (immutable):
application_shared(default) — any user in the application can read and query the resource. Only the creator can mutate (upsert/delete items).user_private— only the creator can read, query, or mutate the resource. Other users receive a 404 (existence is not leaked). Use this for privacy-sensitive use cases like encrypted personal search.
Direct Vector Support
By default, the server generates embeddings from text using the resource’s model. You can also provide pre-computed vectors directly, bypassing server-side embedding generation. This enables client-side embedding workflows (e.g., local models, encrypted/rotated vectors for privacy-preserving search).
- Upsert: each item can have
text(server embeds),vector(pre-computed), or both (vector takes precedence). - Query: provide
query(text, server embeds) orquery_vector(pre-computed). At least one is required.
Create Resource
POST /v1/embeddings/resources
With a model (server-side embeddings):
{
"key": "my-faq",
"name": "FAQ Knowledge Base",
"model": "openai/text-embedding-3-small",
"description": "Company FAQ entries",
"access_mode": "application_shared",
"items": [
{
"item_id": "faq-1",
"text": "How do I reset my password?",
"metadata": { "category": "account" }
}
]
}Without a model (direct vectors only):
{
"key": "encrypted-search",
"name": "Encrypted Chat Search",
"dimensions": 384,
"access_mode": "user_private"
}When model is omitted, dimensions is required. The resource only accepts pre-computed vectors — text items and text queries will be rejected with a 400 error. This is designed for client-side embedding workflows (e.g., local models, encrypted search with e5-small).
| Field | Type | Required | Description |
|---|---|---|---|
key | string | yes | Unique resource identifier |
name | string | yes | Display name |
model | string | no | Provider-prefixed embedding model. Required for server-side embedding; omit for direct-vector-only resources. |
dimensions | number | conditional | Vector dimensions. Required when model is omitted. When model is provided, dimensions are auto-resolved (or can be overridden). |
description | string | no | Optional description |
access_mode | string | no | application_shared (default) or user_private |
items | array | no | Initial items to populate the resource. When model is omitted, all items must include a vector field. |
Each item in items:
| Field | Type | Required | Description |
|---|---|---|---|
item_id | string | yes | Unique item identifier |
text | string | no | Text content (server generates embedding). Not allowed on model-free resources. |
vector | number[] | no | Pre-computed embedding vector |
metadata | object | no | Arbitrary metadata |
At least one of text or vector must be provided per item. On model-free resources, only vector is accepted.
List Resources
GET /v1/embeddings/resources
Returns all resources visible to the authenticated user. user_private resources are only visible to their creator.
Get Resource
GET /v1/embeddings/resources/:key
Returns a single resource by key. Returns 404 for user_private resources owned by another user.
Update Resource
PATCH /v1/embeddings/resources/:key
{
"name": "Updated Name",
"description": "Updated description"
}Only name and description can be updated. access_mode is immutable.
Delete Resource
DELETE /v1/embeddings/resources/:key
Deletes the resource and all its items and vectors.
Upsert Items
POST /v1/embeddings/resources/:key/items
{
"items": [
{
"item_id": "doc-1",
"text": "Refunds are processed within 30 days.",
"metadata": { "category": "policy" }
},
{
"item_id": "doc-2",
"vector": [0.1, 0.2, 0.3, ...],
"metadata": { "source": "local-model" }
}
]
}Each item must have at least one of text or vector. When vector is provided, the server skips embedding generation for that item. Vector dimensions must match the resource’s embedding_dimensions. On model-free resources, all items must use vector — text items are rejected with a 400 error.
Bulk Upsert Items
POST /v1/embeddings/resources/:key/items/bulk-upsert
High-throughput variant of POST /v1/embeddings/resources/:key/items for large client-side imports (e.g. first-time indexing of multi-million-vector datasets). Accepts up to 10,000 items and a 50 MB request body on this route only (the single-batch endpoint stays at the default 25 MB limit).
Pre-computed vectors only. Items containing a text field are rejected batch-wide with HTTP 400 and code: "bulk_text_not_supported". Use the single-batch endpoint if you need server-side embedding generation.
{
"items": [
{
"item_id": "frag-0001",
"vector": [0.1, 0.2, 0.3, ...],
"metadata": { "source": "chatgpt-export" }
},
{
"item_id": "frag-0002",
"vector": [0.4, 0.5, 0.6, ...]
}
]
}Response (full success — HTTP 200):
{
"object": "embedding_resource_items_bulk",
"resource_key": "chat-search-v4-...",
"upserted": 9999,
"failed": []
}Partial success (HTTP 207 Multi-Status): per-item validation failures are quarantined into failed[] while valid items are still persisted. Inspect failed[] regardless of status to discover per-item errors.
{
"object": "embedding_resource_items_bulk",
"resource_key": "chat-search-v4-...",
"upserted": 9950,
"failed": [
{ "item_id": "frag-bad-1", "error": "vector_dimension_mismatch" },
{ "error": "missing_item_id" }
]
}Error codes in failed[].error:
| Code | Meaning |
|---|---|
missing_item_id | item_id missing or not a non-empty string |
vector_missing | Item has no vector field (and no text, which is disallowed here) |
vector_dimension_mismatch | vector length does not match the resource’s embedding_dimensions |
vector_non_numeric | vector contains non-finite values (NaN, Infinity, or non-numbers) |
Idempotency. Upserts are idempotent by item_id, so retrying on network errors is safe — previously stored items will simply be overwritten.
Async Ingestion (multi-million items)
For imports large enough that the sync bulk-upsert endpoint hits HTTP gateway timeouts (typically >2 M items), use the async ingestion flow. Clients upload an NDJSON file of pre-computed vectors directly to S3 via a presigned URL, then enqueue an ingestion job that processes the file in the background. Poll the job for progress.
Four-step flow.
-
Request a presigned upload URL.
POST /v1/files/presigned-upload{ "purpose": "embedding_ingestion", "content_type": "application/x-ndjson", "content_encoding": "gzip", "content_length_hint": 524288000, "filename_hint": "chat-export-2026-04-17.ndjson.gz" }Response:
{ "object": "file_presigned_upload", "file_id": "01J7XY…", "url": "https://s3.eu-central-1.amazonaws.com/…?X-Amz-Signature=…", "headers": { "Content-Type": "application/x-ndjson" }, "expires_at": "2026-04-17T12:15:00Z", "storage_key": "org/…/app/…/purpose/embeddings/01J7XY…" }Clients must complete the S3 PUT within the 15-minute
expires_atwindow. -
PUT the gzipped NDJSON directly to S3. One line per item:
{"item_id": "...", "vector": [...], "metadata": {...}}. Alternatively use"vector_b64"with a base64-packedFloat32Arrayto halve the payload size. -
Confirm the upload.
POST /v1/files/:file_id/confirmResponse
{object: "file_confirm_upload", status: "ready", size_bytes, confirmed_at}. Idempotent — re-calling a confirmed file returns the same response. Returns 409 if the S3 object is missing or empty. -
Enqueue the ingestion job.
POST /v1/embeddings/resources/:key/ingestion-jobs{ "file_id": "01J7XY…", "job_id": "client-uuid-for-idempotency", "total_items_hint": 2000000 }Response (HTTP 202 on fresh enqueue, HTTP 200 on idempotent duplicate):
{ "object": "embedding_ingestion_job", "job_id": "client-uuid-for-idempotency", "resource_key": "chat-search-v4-…", "status": "queued", "created_at": "2026-04-17T12:00:05Z" } -
Poll for completion.
GET /v1/embeddings/resources/:key/ingestion-jobs/:job_id{ "object": "embedding_ingestion_job", "job_id": "…", "resource_key": "chat-search-v4-…", "status": "processing", "total_items": 2000000, "processed_items": 823421, "upserted_items": 823408, "failed_items": [ { "item_id": "chat123_42_3", "error": "vector_dimension_mismatch" } ], "fatal_error": null, "created_at": "…", "started_at": "…", "finished_at": null }Recommended polling interval: 5 seconds.
failed_items[]is capped at the first 1,000 entries. Terminal states:done,failed,cancelled. -
(Optional) Cancel an in-flight job.
DELETE /v1/embeddings/resources/:key/ingestion-jobs/:job_idMarks the job cancelled; the worker bails out between batches. Returns 409 if the job already reached a terminal state.
Billing. One meter fires per successful job, with endpointPath: /v1/embeddings/resources/:resourceKey/ingestion-jobs and bytes = dimensions × upserted_items × 4. Failed jobs do not meter beyond whatever was successfully persisted at the time of failure.
Retention. Job rows + uploaded S3 files are deleted 30 days after completion.
List Items
GET /v1/embeddings/resources/:key/items?limit=50
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | no | Maximum items to return |
Delete Item
DELETE /v1/embeddings/resources/:key/items/:itemId
Query Resource
POST /v1/embeddings/resources/:key/query
Semantic similarity search. At least one of query or query_vector is required.
{
"query": "How long do refunds take?",
"top_k": 5,
"min_score": 0.7
}Or with a pre-computed query vector:
{
"query_vector": [0.1, 0.2, 0.3, ...],
"top_k": 5
}| Field | Type | Required | Description |
|---|---|---|---|
query | string | no | Text query (server generates embedding) |
query_vector | number[] | no | Pre-computed query vector |
top_k | number | no | Maximum results to return |
min_score | number | no | Minimum similarity score threshold |
filter | object | no | Metadata filter |
Response:
{
"data": [
{
"item_id": "doc-1",
"score": 0.92,
"excerpt": "Refunds are processed within 30 days.",
"metadata": { "category": "policy" }
}
]
}Error Responses
| Scenario | Status | Message |
|---|---|---|
| No model and no dimensions | 400 | ”dimensions is required when model is not provided” |
Invalid access_mode | 400 | ”access_mode must be ‘application_shared’ or ‘user_private‘“ |
Item with neither text nor vector | 400 | ”Each item must have either ‘text’ or ‘vector‘“ |
| Text item on model-free resource | 400 | ”Cannot embed text items — resource has no model. Use the ‘vector’ field for pre-computed embeddings.” |
| Text query on model-free resource | 400 | ”Cannot generate query embedding — resource has no model. Use ‘query_vector’ for pre-computed query vectors.” |
Neither query nor query_vector | 400 | ”Either ‘query’ or ‘query_vector’ is required” |
| Vector dimension mismatch (upsert) | 400 | ”Vector dimensions (N) do not match resource embedding_dimensions (M)“ |
| Vector dimension mismatch (query) | 400 | ”Query vector dimensions (N) do not match resource embedding_dimensions (M)“ |
Non-owner access to user_private resource | 404 | ”Embedding resource not found” |
Non-owner mutation of application_shared resource | 403 | ”Only the resource creator can mutate this resource” |
Bulk upsert contains any text item | 400 | ”Bulk endpoint does not support text items; use the single-batch endpoint for server-side embedding.” (code: "bulk_text_not_supported") |
| Bulk upsert: per-item validation failure | 207 | Valid items persisted; invalid items returned in failed[] with machine-readable error codes |