Embeddings
POST /v1/embeddings turns text into 1024-dimensional vectors using the
same text embeddings the knowledge base uses for retrieval. The request and
response follow the OpenAI embeddings shape, so client.embeddings.create()
in the official SDKs works unchanged.
POST https://api.chatbots.ecourtdate.com/v1/embeddings
Scope: chat
You do not need this endpoint to use bots: ingested files and crawled pages are embedded automatically. Use it to build your own semantic search, deduplication, or clustering over text that is not in the knowledge base, or to search your own vector store with vectors that are compatible with the ones the API uses.
Request
curl -s "https://api.chatbots.ecourtdate.com/v1/embeddings" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "default",
"input": [
"Jury duty begins at 8:30 a.m. in the second-floor assembly room.",
"Filing fees may be paid online or at the clerk of court's office."
],
"input_type": "search_document"
}'
| Field | Type | Description |
|---|---|---|
input | string or array of strings | Required. The text to embed. An array may hold 1 to 96 strings; each string must be 1 to 100,000 characters. An empty string, or an empty item, is rejected with 400. Token arrays are not supported. |
model | string | Optional. The API serves one embedding model, so this value does not select anything; it is echoed back unchanged in the response model (default when omitted or null). The OpenAI SDKs require it; pass any label, such as default. |
input_type | string | eCourtDate extension. search_document (default) for text you will store and search over, search_query for a query you will match against stored vectors. See below. |
encoding_format | string | float (default) returns JSON arrays of numbers; base64 returns each vector as a base64 string of little-endian 32-bit floats (4096 bytes per vector). |
dimensions | integer | Not supported: every vector has 1024 dimensions. Omit it; the JSON integer 1024 is accepted, any other value is rejected with 400. |
user | string | Accepted for compatibility and ignored. |
Unknown fields are ignored. Values must be JSON-typed: "dimensions": "1024"
and "dimensions": 1024.0 are rejected.
input_type
The embedding model produces slightly different vectors for passages that
will be searched and for the queries that search them. For the best
similarity scores, embed stored content with search_document and embed
each search string with search_query, then compare. Using the same type
for both still works; the ranking is just less sharp.
With the OpenAI SDKs, pass input_type through extra_body:
response = client.embeddings.create(
model="default",
input="When does jury duty start?",
extra_body={"input_type": "search_query"},
)
const response = await client.embeddings.create({
model: "default",
input: "When does jury duty start?",
// @ts-expect-error eCourtDate extension
input_type: "search_query",
});
Response
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [0.0213, -0.0147, 0.0391, …]
},
{
"object": "embedding",
"index": 1,
"embedding": [-0.0082, 0.0305, -0.0119, …]
}
],
"model": "default",
"usage": {
"prompt_tokens": 31,
"total_tokens": 31
}
}
(Each vector is abbreviated; a real response contains 1024 numbers per entry.)
| Field | Type | Description |
|---|---|---|
object | string | Always list. |
data[].object | string | Always embedding. |
data[].index | integer | Position of the input this vector belongs to, 0-based. Entries are returned in input order. |
data[].embedding | array of numbers, or string | The 1024-dimensional vector: a JSON array with encoding_format: "float", a base64 string with "base64". |
model | string | The model label you sent, unchanged, or default when you sent none. |
usage.prompt_tokens | integer | Tokens in the input. Counts toward your daily token quota. |
usage.total_tokens | integer | Same as prompt_tokens; embeddings generate no output tokens. |
There are no other keys.
Base64 encoding
encoding_format: "base64" reduces the response size. Decode each string to
bytes and read 1024 little-endian IEEE 754 single-precision floats:
import base64
import struct
def decode(embedding: str) -> list[float]:
raw = base64.b64decode(embedding)
return list(struct.unpack("<1024f", raw))
The OpenAI Python SDK requests base64 by default and decodes it for you,
so response.data[0].embedding is a list of floats in SDK code. Values
decoded from base64 are 32-bit precision (0.1 comes back as
0.10000000149011612); pass encoding_format="float" if you need the
values exactly as the model produced them.
A similarity example
Embed a handful of FAQ answers once with search_document, embed each
incoming question with search_query, and pick the closest answer by cosine
similarity. With the OpenAI Python SDK:
import math
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["API_KEY"],
base_url="https://api.chatbots.ecourtdate.com/v1",
)
faq = {
"jury-start": "Jurors report to the second-floor assembly room by 8:30 a.m.",
"fees-online": "Filing fees can be paid online through the payment portal or at the clerk's office.",
"traffic-hours": "The Traffic Division is open Monday through Friday, 8:00 a.m. to 4:30 p.m.",
"courthouse-address": "The courthouse is at 100 Main Street; public parking is in the Fifth Street garage.",
}
# Embed the stored answers once (search_document).
docs = client.embeddings.create(
model="default",
input=list(faq.values()),
encoding_format="float",
extra_body={"input_type": "search_document"},
)
vectors = {key: item.embedding for key, item in zip(faq, docs.data)}
def cosine(a: list[float], b: list[float]) -> float:
dot = sum(x * y for x, y in zip(a, b))
return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))
def best_match(question: str) -> tuple[str, float]:
# Embed the question as a query (search_query).
query = client.embeddings.create(
model="default",
input=question,
encoding_format="float",
extra_body={"input_type": "search_query"},
).data[0].embedding
scored = {key: cosine(query, vector) for key, vector in vectors.items()}
key = max(scored, key=scored.get)
return key, scored[key]
print(best_match("What time do I need to be there for jury duty?"))
# ('jury-start', 0.87)
print(best_match("Where can I park?"))
# ('courthouse-address', 0.81)
Scores are illustrative; treat cosine similarity as a ranking signal and
choose thresholds from your own data. Store vectors with the input_type
they were produced with, and never mix vectors from different embedding
models in one index.
Batch stored content 96 strings per request, and keep each string to a passage (a paragraph or a few sentences) rather than a whole document: one vector per passage gives far better retrieval than one vector per file.
Limits
| Limit | Value |
|---|---|
| Strings per request | 96 |
| Characters per string | 100,000 |
| Request body | 5,242,880 bytes (5 MiB), inclusive |
| Vector dimensions | 1024 |
Errors
| Status | code | When |
|---|---|---|
400 | null | Validation: input missing, not a string or array, an empty string, a string over 100,000 characters, or an empty array (param: "input"); an item that is empty, not a string, or over 100,000 characters (param: "input[i]"); more than 96 items (param: "input[96]"); dimensions other than the integer 1024 (param: "dimensions"); an unknown encoding_format or input_type, or a non-string model or user (param names the field); a body that is not a JSON object or malformed JSON (param: null). |
401 | invalid_api_key | Missing or invalid key. |
403 | insufficient_scope | The key lacks the chat scope. |
413 | request_too_large | Body over 5 MiB. |
429 | rate_limit_exceeded | Per-minute request limit reached, or the text embedding service is rate limited. Honor Retry-After. |
429 | insufficient_quota | Daily token quota reached. |
500 | null | type: server_error. Unexpected failure; retry with backoff. |
503 | upstream_unavailable | The text embedding service timed out or is unavailable; retry after Retry-After. |
The OpenAI SDKs map these to BadRequestError, AuthenticationError,
PermissionDeniedError, RateLimitError, and APIStatusError as usual.
All errors use the envelope described in Errors.