Chat API
Reference for Cencori chat completions across the official SDK and OpenAI-compatible HTTP endpoints.
Overview
Cencori exposes chat completions through two public HTTP surfaces:
Both surfaces authenticate with your Cencori project key and log requests to the same project dashboard.
When to Use Which Endpoint
Choose based on your use case:
The OpenAI-compatible base URL should usually stop at /v1; most clients append /chat/completions themselves.
Official TypeScript SDK
import { Cencori } from 'cencori';
const cencori = new Cencori({
apiKey: process.env.CENCORI_API_KEY,
});
const response = await cencori.ai.chat({
model: 'gpt-6-sol',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'What is the capital of France?' },
],
temperature: 0.2,
maxTokens: 300,
});
console.log(response.content);
console.log(response.toolCalls);
console.log(response.usage.totalTokens);SDK Response Shape
{
"id": "chatcmpl_123",
"model": "gpt-6-sol",
"content": "The capital of France is Paris.",
"toolCalls": null,
"finishReason": "stop",
"usage": {
"promptTokens": 13,
"completionTokens": 7,
"totalTokens": 20
}
}Native Cencori HTTP Endpoint
Use the native endpoint when you want direct access to /api/ai/chat:
curl https://cencori.com/api/ai/chat \
-H "CENCORI_API_KEY: csk_..." \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-sol",
"messages": [{"role": "user", "content": "Hello!"}],
"stream": false
}'This endpoint returns the OpenAI-compatible choices[0].message shape and also includes Cencori convenience fields such as content, toolCalls, and cost_usd.
OpenAI-Compatible Endpoint
Use this when a client or framework already expects the OpenAI Chat Completions API:
curl https://api.cencori.com/v1/chat/completions \
-H "Authorization: Bearer csk_..." \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-sol",
"messages": [{"role": "user", "content": "Hello!"}]
}'When configuring an SDK, set the base URL to https://api.cencori.com/v1, not the full /chat/completions URL.
Request Parameters
OpenAI-Compatible Response Shape
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-6-sol",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The capital of France is Paris."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 13,
"completion_tokens": 7,
"total_tokens": 20
}
}Knowledge bases (RAG)
Name up to three knowledge bases and Cencori searches them with the user's latest message before calling the model. The best-matching passages are added to the prompt, the model is told to cite them with [1]-style markers, and the response says which passage each marker points to.
With the SDK (cencori 1.9.0 or later), create a base, upload a file, and ask:
const kb = await cencori.knowledge.createBase({ name: 'Support docs' });
const pdf = new Blob([await fs.readFile('refund-policy.pdf')], { type: 'application/pdf' });
await cencori.knowledge.addFileSource(kb.id, pdf, { filename: 'refund-policy.pdf' });
const response = await cencori.ai.chat({
model: 'gpt-6-sol',
messages: [{ role: 'user', content: 'How long do refunds take?' }],
knowledgeBases: [kb.id],
});
console.log(response.content); // "Refunds are processed within 5 business days [1]."
console.log(response.knowledge?.citations); // [{ n: 1, sourceId: 'src_...', page: 2, score: 0.91, ... }]Uploads take PDF, DOCX, Markdown, HTML, CSV, JSON or plain text, up to 8MB a file, or raw text with addInlineSource(kb.id, text). Indexing runs after the upload returns: a source is usable once getSource() reports status: 'ready'. Uploads that look like prompt injection are held as stale for review instead of indexed.
Over HTTP:
curl https://api.cencori.com/v1/chat/completions \
-H "Authorization: Bearer $CENCORI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-sol",
"messages": [{ "role": "user", "content": "How long do refunds take?" }],
"knowledge_bases": ["kb_8f2c..."]
}'The response carries a knowledge object next to choices:
{
"choices": [{ "message": { "role": "assistant", "content": "Refunds are processed within 5 business days [1]." } }],
"knowledge": {
"knowledge_bases": ["kb_8f2c..."],
"citations": [
{ "n": 1, "chunk_id": "4b1e...", "source_id": "src_91ad...", "ord": 3, "page": 2, "score": 0.91 }
]
}
}When streaming, the same citations arrive as JSON in the X-Cencori-Knowledge-Citations response header, before the first token. With cencori.ai.chatStream(), the first chunk carries them as chunk.citations.
- Up to 5 passages are used per request, the best matches across all named bases.
- Requires a secret project key. Publishable (browser) keys get
403 secret_key_required. - Only project-wide knowledge bases can be used here. Tenant-scoped bases return
403 tenant_scope_mismatch; query those through a session on their agent installation. - An unknown id returns
404 knowledge_base_not_found. - Retrieval fails open: if search is unavailable, the request still runs, just without passages, and
citationsis empty. - The query embedding is billed like any embedding call. Grounded requests skip the prompt cache, so answers reflect the current documents.
Streaming
SDK Streaming
const stream = cencori.ai.chatStream({
model: 'gpt-6-sol',
messages: [{ role: 'user', content: 'Tell me a story.' }],
});
for await (const chunk of stream) {
process.stdout.write(chunk.delta);
}HTTP Streaming
curl -N https://api.cencori.com/v1/chat/completions \
-H "Authorization: Bearer csk_..." \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-sol",
"messages": [{"role": "user", "content": "Tell me a story."}],
"stream": true
}'Tool Calling
const response = await cencori.ai.chat({
model: 'gpt-6-sol',
messages: [{ role: 'user', content: 'What is the weather in Tokyo?' }],
tools: [
{
type: 'function',
function: {
name: 'get_weather',
description: 'Get weather for a location',
parameters: {
type: 'object',
properties: {
location: { type: 'string' },
},
required: ['location'],
},
},
},
],
});
console.log(response.toolCalls);Error Handling
The SDK throws typed errors — never a bare message. Every failure carries the
HTTP status, the machine-readable code, the requestId for support
correlation, and any retry hint the server sent. Transient failures (rate
limits, 5xx, network blips) retry automatically with backoff, honoring the
server's Retry-After; 4xx fail fast — except 429s, which retry then surface
as RateLimitError with the hint intact.
import { CencoriError, RateLimitError } from 'cencori';
try {
await cencori.ai.chat({
model: 'gpt-6-sol',
messages: [{ role: 'user', content: 'Hello!' }],
});
} catch (error) {
if (error instanceof RateLimitError) {
console.log('back off for', error.retryAfterMs, 'ms');
} else if (error instanceof CencoriError) {
// error.statusCode, error.code, error.requestId, error.isRetryable
console.log(error.code, error.requestId);
}
}Safety classification
Every chat response carries what the input guards verdicts — so a 200 whose model resisted an attack is distinguishable from a request that was never scanned:
{
"choices": [ ... ],
"safety": {
"scanned": true,
"input": { "safe": false, "layer": "jailbreak", "riskScore": 0.86, "reasons": ["..."] }
}
}Streams carry the same verdict as headers (X-Cencori-Safety-Scanned,
X-Cencori-Safety-Input, X-Cencori-Safety-Score). scanned: false means
the request ran the passthrough lane (your own safety layers apply). Only
category-level reasons are exposed — never matched excerpts.
Auto-router (auto / cencori-auto)
Pass model: 'auto' to route by task across the project's active BYOK keys only — image content → vision model, code/tool calls → code-capable model, analytical prompts → reasoning model, otherwise a cheap fast model. No usable BYOK key returns 402 byok_required; it never falls back to managed keys and works at a zero credit balance. cencori-auto forces the BYOK router when a Tensor plan mapping would otherwise own bare auto. Details: BYOK auto-router.

