Make your first Kendr API request in under five minutes.
Sign in, fund the Kendr wallet, create one key, and call frontier models through AWS Bedrock or native provider routes without distributing those provider credentials to users or applications. Existing OpenAI clients usually need only a new base URL, Kendr API key, and Kendr model alias.
Quickstart: one key, one request
What you need: a funded Kendr account and a scoped API key created in API keys. Save the key when it is created; its full value is shown once. Keep it on your server or in a local environment variable, never in browser code.
- 1Set your keyexport KENDR_API_KEY="kndr_live_..." on macOS/Linux, or $env:KENDR_API_KEY="kndr_live_..." in PowerShell.
- 2Send a chat completionUse kendr-intelligent for request-by-request routing, or replace it with an available alias returned by GET /v1/models.
- 3Keep the receiptRead the standard response fields plus kendr_usage and, for routed requests, kendr_routing.
curl https://api.kendr.org/v1/chat/completions \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kendr-intelligent",
"messages": [{"role": "user", "content": "Reply with exactly five words."}]
}'
A successful response uses the OpenAI Chat Completions shape and adds Kendr settlement metadata. For production retries, also send a stable Idempotency-Key; the server generates a request identifier when you omit one.
Raw HTTP calls start with https://api.kendr.org. OpenAI-compatible SDKs normally use https://api.kendr.org/v1 because the SDK appends /chat/completions, /responses, or /models.
Connect a harness
Kendr speaks the OpenAI and Anthropic wire formats, so most agents, IDEs, and SDKs connect by pointing their base URL at https://api.kendr.org and pasting one scoped key. Use model: "kendr-intelligent" for general managed routing with optimized context, kendr-intelligent-direct for general routing with original context, kendr-coder for coding-focused routing with optimized context, or kendr-coder-direct for coding-focused routing with original context. You can also use an exact or account-owned alias returned by GET /v1/models. Unknown aliases fail closed so typos cannot silently change routing or billing. Paste the key you saved at creation in place of <YOUR_KEY> below — full keys are never shown again.
Claude Code — point the Anthropic base URL at Kendr
export ANTHROPIC_BASE_URL=https://api.kendr.org export ANTHROPIC_AUTH_TOKEN=<YOUR_KEY> claude
OpenAI Python SDK with the Kendr base URL
from openai import OpenAI
client = OpenAI(base_url="https://api.kendr.org/v1", api_key="<YOUR_KEY>")
resp = client.chat.completions.create(
model="kendr-intelligent",
messages=[{"role": "user", "content": "Say hi in five words."}],
)
print(resp.choices[0].message.content)
Plain curl against the OpenAI-compatible endpoint
curl https://api.kendr.org/v1/chat/completions \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json" \
-d '{"model": "kendr-intelligent", "messages": [{"role": "user", "content": "Say hi in five words."}]}'
Cursor — override the OpenAI base URL
# Cursor -> Settings -> Models -> API keys # OpenAI API key: <YOUR_KEY> # Override base URL: https://api.kendr.org/v1 # Then pick any model and send a chat message.
Anthropic Python SDK with the Kendr base URL
import anthropic
client = anthropic.Anthropic(base_url="https://api.kendr.org", auth_token="<YOUR_KEY>")
msg = client.messages.create(
model="kendr-intelligent", max_tokens=64,
messages=[{"role": "user", "content": "Say hi in five words."}],
)
print(msg.content)
Any OpenAI-compatible agent — add Kendr as a custom provider
// Works with agents that accept a custom OpenAI-compatible provider
// (chat-completions API mode). Keep the key in an environment variable.
{
"providers": {
"kendr": {
"baseUrl": "https://api.kendr.org/v1",
"api": "openai-completions",
"apiKey": "$KENDR_KEY",
"models": [{ "id": "kendr-intelligent", "name": "Kendr Intelligent" }]
}
}
}
// shell
export KENDR_KEY=<YOUR_KEY>
Requests stream over SSE in each vendor's wire format, and /v1/messages/count_tokens is available for Anthropic-style token counting. Tools you declare execute on Kendr's servers during generation; harness-local tool execution (for example Claude Code editing files on your machine) is not yet supported, so coding agents work best today for chat, review, and planning workflows.
Know the pricing and credit policy
All model aliases and routing modes use one fixed 5% markup on the configured provider cost. Exact aliases and Kendr managed routing do not have different markup percentages. Credits added by a completed paid package purchase never expire; promotional grants can have a grant-specific expiration.
- Use the public catalog's pricing_policy and per-model pricing fields for public quotes.
- Use authenticated GET /v1/models for models and rates available to the current account.
- Use kendr_usage.credits_charged_micros as the authoritative settled model charge.
- Use the wallet activity or billing summary to distinguish non-expiring purchased lots from expiring promotional lots.
See Credits and billing for package and lot behavior.
Frontier models through Kendr Cloud
The model gateway accepts Authorization: Bearer kndr_live_... and exposes the model aliases currently available to your Kendr account. Existing desktop clients can keep using /api/v1/llm/*; new integrations can use the standard /v1/* routes.
| Route | Purpose |
|---|---|
| GET /api/public/models | Public, unauthenticated catalog for crawlable model pages and pre-login discovery. It omits private routes and account-specific aliases. |
| GET /v1/models | List currently available Kendr model aliases. |
| POST /v1/responses | Generate with the OpenAI Responses shape, including streaming, tools, and optional web search. |
| POST /v1/chat/completions | Use OpenAI-compatible chat clients, SSE chunks, and optional web search. |
| POST /v1/messages | Use the Anthropic Messages request and response shape, including optional streaming. |
| POST /v1/messages/count_tokens | Free Anthropic-compatible token estimate for a message request. |
| POST /v1/video/analyses | Queue an account-scoped asynchronous video analysis. |
| GET /v1/video/analyses/{id} | Poll video analysis state and result. |
| GET /api/v1/openapi.json | Download the model-only OpenAPI contract. |
| GET|POST /api/me/ai/preferences | Read or save default and mode-specific aliases after Kendr login. |
Discover public models without a key
curl https://api.kendr.org/api/public/models
{
"object": "list",
"version": "sha256:<content-digest>",
"last_updated": "<RFC3339 timestamp>",
"pricing_policy": {
"currency": "credits",
"markup_percent": 5,
"normal_markup_percent": 5,
"intelligent_markup_percent": 5,
"quote_basis": "default_available_route"
},
"data": [
{
"id": "<public alias>",
"slug": "<public alias>",
"display_name": "<display name>",
"family": "<model family>",
"mode": "normal",
"context_window": 0,
"latency_tier": "<tier>",
"capabilities": [],
"available": true,
"availability": "available",
"status_reason": "Available for API requests.",
"pricing": {
"currency": "credits",
"credits_per_million_input": 0,
"credits_per_million_cached_input": 0,
"credits_per_million_output": 0,
"tiers": []
}
}
]
}
The values above illustrate the response shape, not a quoted model or price. Read the live payload, retain its version with generated pages or cached records, and refresh when that content digest changes. Use GET /v1/models after authentication because account-specific availability can differ.
curl https://api.kendr.org/v1/chat/completions \
-H "Authorization: Bearer kndr_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-request-001" \
-d '{
"model": "kendr-intelligent",
"web_search": true,
"messages": [{"role": "user", "content": "Summarize this decision."}]
}'
Text model aliases can stream responses and can use web search when the route and account configuration support it. Omit web_search to let Kendr decide from the current request, set it to true to request current context, or set it to false to prohibit search. Idempotency-Key is optional on /v1/* requests — one is generated per request when absent, and supplying your own stable key makes retries replay the settled result instead of charging again. Successful responses include the final Kendr usage record.
Choose a Kendr Route through the API
Kendr Routes bundle model selection and context behavior into the model alias. Choose an optimized alias when Kendr Optimizer should prepare eligible context before the provider call, or choose the matching Direct alias when the provider must receive the original context. Do not add a client-selected optimization policy to a managed-route request.
| Product | Alias | Routing pool | Context behavior |
|---|---|---|---|
| Kendr Intelligent | kendr-intelligent | General | Optimized |
| Kendr Intelligent Direct | kendr-intelligent-direct | General | Original context |
| Kendr Coder | kendr-coder | Coding-focused | Optimized |
| Kendr Coder Direct | kendr-coder-direct | Coding-focused | Original context |
An optimized managed route always uses its server-controlled Kendr Optimizer policy. A Direct route always preserves original context. The legacy optimization request object is a compatibility control for non-managed routes only and cannot contradict a managed product's behavior; new integrations should select one of the four aliases above.
Optimized coding request
curl https://api.kendr.org/v1/responses \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Idempotency-Key: coder-request-001" \
-H "Content-Type: application/json" \
-d '{
"model": "kendr-coder",
"input": "Review the implementation and propose a safe patch."
}'
JavaScript: general routing with original context
const response = await fetch("https://api.kendr.org/v1/responses", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KENDR_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "kendr-intelligent-direct",
input: "Summarize the relevant decisions in this project context."
})
});
if (!response.ok) throw new Error(`Kendr request failed: ${response.status}`);
const result = await response.json();
console.log(result.output_text);
console.log(result.kendr_usage.credits_charged_micros);
Python: coding routing with original context
import os
import uuid
import requests
response = requests.post(
"https://api.kendr.org/v1/responses",
headers={
"Authorization": f"Bearer {os.environ['KENDR_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"model": "kendr-coder-direct",
"input": "Review the implementation and propose a safe patch.",
},
timeout=180,
)
response.raise_for_status()
result = response.json()
print(result["output_text"])
Read the optimization receipt
{
"kendr_usage": {
"input_tokens": 1200,
"output_tokens": 180,
"credits_charged_micros": 180000
},
"kendr_optimization": {
"enabled": true,
"requested_mode": "balanced",
"resolved_mode": "balanced",
"engine": "kendr_optimizer",
"original_input_tokens_estimated": 1800,
"optimized_input_tokens_estimated": 1200,
"estimated_input_tokens_avoided": 600,
"estimated_credits_saved_micros": 40000,
"estimated_credits_without_optimization_micros": 220000,
"quality_guard": {"passed": true, "checks": ["latest_user_request_preserved"], "violations": []},
"shadow": false
}
}
- kendr_usage.credits_charged_micros is the authoritative settled charge. Divide microcredits by 1,000,000 to display Kendr credits.
- estimated_credits_saved_micros is the rate-card-priced counterfactual saving from estimated avoided input tokens. Do not subtract it again; the settled charge already uses the optimized provider request.
- estimated_credits_without_optimization_micros is the comparable estimated charge without that input reduction.
- shadow: true means the deployment is running a preview-only evaluation. The provider received the original request, so no saving was applied.
- A zero saving is valid for short requests, protected content, structured-output/tool contracts, rejected quality checks, or unavailable semantic engines. Check fallback_reason, skipped_strategies, and quality_guard.violations.
Optimized managed routes can return kendr_optimization with the server-selected engine, estimated input reduction, quality checks, and fallback state. Direct routes preserve original context and must not be presented as optimized.
Streaming
Set stream: true on the same alias-based request. The final done event contains settled usage and, for optimized routes, the complete kendr_optimization receipt; token deltas do not contain billing information. Use /api/v1/llm/responses when you need Kendr-native named SSE events. Safe routes relay provider deltas live. Tool combinations that cannot preserve a provider-owned trace safely return one complete JSON response on the Kendr-native endpoint, with X-Kendr-Stream-Mode: buffered and X-Kendr-Buffered-Reason; compatibility endpoints retain their vendor SSE wire format.
Model request fields
Use the same fields across /v1/responses, /v1/chat/completions, /v1/messages, and /api/v1/llm/responses where the compatibility format allows them.
| Field | Required | Use |
|---|---|---|
| model | Yes | Kendr alias such as kendr-intelligent or a Normal text model alias from GET /v1/models. |
| input or messages | Yes | Prompt text or role-based chat messages, depending on the route format. |
| instructions | No | System-level guidance for Responses-compatible requests. |
| max_output_tokens or max_tokens | No | Caps generated tokens for the selected route. |
| stream | No | Set true to receive server-sent events. |
| web_search | No | Tri-state control: omit for automatic selection from the current request, use true to request search, or false to prohibit it. |
| tools | No | Tool declarations executed on the server side. Use {"type": "kendr_app", "selection": "auto"} to let Kendr discover and relevance-filter OAuth apps linked to the signed-in account. Use the separate {"type": "kendr_mcp", "server_id": "mcp_..."} contract to activate one registered, trusted custom MCP server (see Call remote MCP tools). Credentials remain server-side, and pending tool calls are never returned to the client. |
| conversation_id | No | Stable identifier for a conversation. Kendr managed routing stores the selected model per conversation and reuses it on later turns (kendr_routing.source: "sticky"), skipping router latency and cost. Omit it and every request routes from scratch. Not accepted on /v1/messages. |
| request_id | No | Body-level alternative to the Idempotency-Key header; idempotency_key is also accepted. All three are optional — a request ID is generated when none is supplied. |
| response_format | No | Structured-output preference for routes that support it. |
| metadata | No | Application metadata. metadata.require_web_search makes search mandatory; metadata.intelligent_reroute selects a model again instead of reusing a compatible conversation route. |
Use web search with models
Text aliases expose web_search in the model catalog when the capability is available. Kendr supports provider-native web search on compatible routes and a managed cross-provider web-search gateway when configured.
curl -N https://api.kendr.org/v1/responses \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Idempotency-Key: web-search-001" \
-H "Content-Type: application/json" \
-d '{
"model": "kendr-intelligent",
"input": "What changed in the web search provider landscape today?",
"web_search": true,
"stream": true
}'
- Use web_search: true to request current web grounding.
- Use web_search: false for private/offline prompts that must not use web search.
- Use metadata.require_web_search: true only when the request should fail rather than answer without web search.
- When an answer uses web results, show source URLs returned in the model output or cited context.
Call remote MCP tools
OAuth app connectors and custom MCP servers are different integrations. App connections are discovered from the authenticated account with {"type": "kendr_app", "selection": "auto"}; Kendr managed routing retains only apps relevant to the current request before it chooses a tool-capable route. The MCP contract below is for a specific custom server chosen by ID.
Kendr tool calling is server-executed. You attach a registered MCP server to a request; Kendr injects the server URL and its encrypted authorization on the backend, the model calls the MCP tools during generation, and you receive only the final assistant text. There is no client-side function-calling loop: the API never returns a pending tool call for your code to execute, and it has no endpoint for submitting tool results back.
1. Register the server once
Create the server under your account with POST /api/me/mcp-servers (or the MCP servers page in the web app). Registration is a signed-in account action: authenticate with the X-Kendr-Session token from app login, the browser session cookie, or an OAuth token carrying the app scope. API keys cannot manage MCP servers — they only invoke models. The URL must be public HTTPS without embedded credentials, and the server must be marked trusted before it can be enabled. The optional authorization token is stored encrypted (AES-256-GCM) and is never returned by any API.
curl https://api.kendr.org/api/me/mcp-servers \
-H "X-Kendr-Session: $KENDR_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Team docs",
"server_label": "team-docs",
"server_url": "https://mcp.example.com/mcp",
"description": "Internal documentation search",
"authorization": "Bearer mcp-token-...",
"allowed_tools": ["search_docs", "read_page"],
"enabled": true,
"trusted": true
}'
The response includes the generated id (mcp_...) you will reference in model requests. allowed_tools optionally restricts which MCP tools the model may call.
2. Reference it in a model request
curl https://api.kendr.org/v1/responses \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Idempotency-Key: mcp-demo-001" \
-H "Content-Type: application/json" \
-d '{
"model": "kendr-intelligent",
"input": "Search the team docs for the deployment runbook and summarize it.",
"conversation_id": "support-thread-8841",
"tools": [
{ "type": "kendr_mcp", "server_id": "mcp_1f2e3d4c5b6a" }
]
}'
Rules and limits
- The server must be enabled and trusted and belong to the authenticated account, otherwise the request fails with 400 "MCP server ... is unavailable, disabled, or not trusted." The rejection happens before any credits are reserved.
- MCP tools require an OpenAI Responses route. With kendr-intelligent, routing automatically restricts candidates to compatible routes; with a Normal alias on an incompatible route the request fails with 400 "The selected model route does not support remote MCP tools. Choose an OpenAI Responses model."
- At most 8 MCP servers can be activated per request.
- Kendr-attested read-only OAuth app tools can stream assistant text while credentials and tool progress remain private. Custom kendr_mcp servers and stateful multi-stage tool combinations return one complete response because their hidden provider trace or side effects cannot safely be replayed after a disconnect.
- Provider-native tool declarations (for example OpenAI function tools) are forwarded to compatible routes, but any unexecuted tool call the model emits is not returned; if a model answers only with a tool call, Kendr managed routing treats the output as incomplete and no credits are charged.
Know which credential belongs where
Kendr has three credential layers. They solve different problems and are intentionally not interchangeable.
| Credential | Who configures it | Where it is sent | Purpose |
|---|---|---|---|
| Kendr API key kndr_live_... | Customer or customer application | Authorization or X-API-Key | Calls model, query, usage, and other API-key-enabled Kendr routes. |
| Kendr session or OAuth token | Kendr login flow | Cookie, X-Kendr-Session, or bearer header | Acts for a signed-in user and can create or revoke Kendr API keys. |
| Connected-service authorization | The signed-in customer | Kendr connection flow only | Allows an explicitly connected app or MCP server to provide data and tools. It is never returned through model API responses. |
Your integration should send a Kendr API key and a Kendr model alias such as kendr-intelligent. It should not send a vendor API key, Secret Manager ARN, or provider model credential.
Use OpenAI, Anthropic, or native HTTPS clients
OpenAI JavaScript client
import OpenAI from "openai";
import crypto from "node:crypto";
const client = new OpenAI({
apiKey: process.env.KENDR_API_KEY,
baseURL: "https://api.kendr.org/v1"
});
const response = await client.responses.create({
model: "kendr-intelligent",
input: "Plan a safe database migration."
}, {
headers: { "Idempotency-Key": crypto.randomUUID() }
});
console.log(response.output_text);
console.log(response.kendr_usage);
OpenAI Python client
import os
import uuid
from openai import OpenAI
client = OpenAI(
api_key=os.environ["KENDR_API_KEY"],
base_url="https://api.kendr.org/v1",
)
response = client.responses.create(
model="kendr-intelligent",
input="Plan a safe database migration.",
extra_headers={"Idempotency-Key": str(uuid.uuid4())},
)
print(response.output_text)
Anthropic-compatible request
curl https://api.kendr.org/v1/messages \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Idempotency-Key: messages-001" \
-H "Content-Type: application/json" \
-d '{
"model": "kendr-intelligent",
"max_tokens": 1200,
"messages": [{"role":"user","content":"Review this plan."}]
}'
Live streaming
curl -N https://api.kendr.org/v1/chat/completions \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Idempotency-Key: stream-001" \
-H "Content-Type: application/json" \
-d '{
"model": "kendr-intelligent",
"stream": true,
"messages": [{"role":"user","content":"Explain the rollout."}]
}'
Exact text routes stream compatible deltas as they arrive. Kendr Routes stream route-selection status first, then stream the selected model's answer and final usage record. Kendr managed routing does not make a second-model verification call.
Migrate an OpenAI client
You do not need to replace the OpenAI Python or JavaScript package. Change the API key, base URL, and model alias; keep the rest of a supported Chat Completions or Responses call intact. Test the exact request fields you use before moving production traffic because Kendr does not claim compatibility with every OpenAI endpoint or every provider-specific extension.
| Setting | Before | With Kendr |
|---|---|---|
| API key | Your provider key | KENDR_API_KEY (kndr_live_...) |
| Base URL | Provider default | https://api.kendr.org/v1 |
| Model | Provider model ID | kendr-intelligent or an alias from GET /v1/models |
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ["KENDR_API_KEY"],
base_url="https://api.kendr.org/v1",
)
result = client.chat.completions.create(
model="kendr-intelligent",
messages=[{"role": "user", "content": "Summarize this design."}],
)
print(result.choices[0].message.content)
Supported compatibility routes are GET /v1/models, POST /v1/chat/completions, and POST /v1/responses. Anthropic-format clients can use POST /v1/messages and POST /v1/messages/count_tokens. File uploads, assistants, fine-tuning, realtime sessions, and other unrelated vendor APIs are not implied by “compatible.”
Use Kendr with the Vercel AI SDK
The Vercel AI SDK has an OpenAI-compatible provider adapter. Configure it on your server with the Kendr /v1 base URL and a Kendr key. Do not expose the key through a client component or public environment variable.
npm install ai @ai-sdk/openai-compatible
import { generateText } from "ai";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
const kendr = createOpenAICompatible({
name: "kendr",
apiKey: process.env.KENDR_API_KEY,
baseURL: "https://api.kendr.org/v1",
includeUsage: true,
});
const { text, usage } = await generateText({
model: kendr.chatModel("kendr-intelligent"),
prompt: "Give me a three-step rollout plan.",
});
console.log(text, usage);
This example uses the Chat Completions wire format. Kendr-specific kendr_usage, routing, and optimization fields may not be surfaced by every third-party adapter; use raw HTTP or the OpenAI client when your application must persist the complete Kendr receipt.
Request structured JSON
Send response_format when the selected model route advertises structured_output in GET /v1/models. Kendr managed routing filters its candidates for that capability. An exact alias without the capability fails instead of silently dropping the schema.
curl https://api.kendr.org/v1/chat/completions \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Idempotency-Key: structured-ticket-001" \
-H "Content-Type: application/json" \
-d '{
"model": "kendr-intelligent",
"messages": [{"role": "user", "content": "Classify: Checkout fails after payment."}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"category": {"type": "string"},
"urgent": {"type": "boolean"}
},
"required": ["category", "urgent"],
"additionalProperties": false
}
}
}
}'
Treat the returned assistant content as untrusted input until your application parses and validates it against the same schema. Compatibility support routes the request to a capable model; it does not replace application-side validation.
Integration rules
| Item | Value |
|---|---|
| Base domain | https://api.kendr.org |
| Public discovery | GET /api/catalog, GET /api/public/models, and GET /api/openapi.json |
| Query execution | POST /api/v1/query; Kendr validates auth and credits, executes the selected surface, then returns a stable Kendr result payload. |
| Primary server auth | Authorization: Bearer kndr_live_... or X-API-Key: kndr_live_... |
| Customer state | GET /api/me/dashboard returns user, packages, api_keys, purchases, ledger, and surfaces |
Quickstart flow
Auth headers accepted by the query route
| Mode | Header | When to use it |
|---|---|---|
| API key | Authorization: Bearer kndr_live_... | Best for server-to-server integrations and background jobs. |
| API key | X-API-Key: kndr_live_... | Alternative header when bearer auth is not convenient. |
| App session | X-Kendr-Session: SESSION_TOKEN | Best when your app just authenticated the user through Kendr. |
| OAuth bearer | Authorization: Bearer kndr_oat_... | Best for Kendr Desktop or CLI flows using PKCE or device code with the app scope. |
| Browser cookie | kendr_session cookie | Works for browser-origin requests after /api/auth/otp/verify. |
Query request body
| Field | Required | Meaning |
|---|---|---|
| surface | Yes | The Kendr surface key, such as web_search, ai_search, web_answer, google_search, google_maps, google_flights, or google_hotels. |
| query | Yes | The primary query string. |
| params | No | An object for optional values such as gl, hl, page, location, or travel fields. |
| Top-level extras | No | Optional params can also be sent beside surface and query. Kendr merges them with params. |
{
"surface": "google_search",
"query": "best llm observability tools",
"params": {
"gl": "us",
"hl": "en",
"page": 1
}
}
Query examples
Kendr is a plain HTTPS and JSON API. The same request contract works from JavaScript, Python, .NET, Java, and Go, so you can use whichever runtime already exists in your stack.
API key auth
curl https://api.kendr.org/api/v1/query \
-X POST \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"surface": "google_search",
"query": "best llm observability tools",
"params": {
"gl": "us",
"hl": "en",
"page": 1
}
}'
App session auth
curl https://api.kendr.org/api/v1/query \
-X POST \
-H "X-Kendr-Session: $KENDR_SESSION" \
-H "Content-Type: application/json" \
-d '{
"surface": "google_maps",
"query": "coworking spaces in austin",
"params": {
"gl": "us",
"hl": "en"
}
}'
Kendr currently ships helper clients for JavaScript and Python. Use the tabs to switch between Curl, JavaScript, Python, .NET, Java, and Go for the same query contract.
Curl request with an API key
curl https://api.kendr.org/api/v1/query \
-X POST \
-H "Authorization: Bearer $KENDR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"surface": "google_search",
"query": "best llm observability tools",
"params": {
"gl": "us",
"hl": "en",
"page": 1
}
}'
Server-side fetch request to POST /api/v1/query
const response = await fetch('https://api.kendr.org/api/v1/query', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KENDR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
surface: 'google_search',
query: 'best llm observability tools',
params: { gl: 'us', hl: 'en', page: 1 }
})
});
const payload = await response.json();
console.log(payload.data, payload.remaining_credits);
Python requests example against the same endpoint
import os
import requests
response = requests.post(
'https://api.kendr.org/api/v1/query',
headers={
'Authorization': f"Bearer {os.environ['KENDR_API_KEY']}",
'Content-Type': 'application/json',
},
json={
'surface': 'google_search',
'query': 'best llm observability tools',
'params': {'gl': 'us', 'hl': 'en', 'page': 1},
},
timeout=30,
)
payload = response.json()
print(payload['data'])
print(payload['remaining_credits'])
.NET HttpClient example
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("KENDR_API_KEY"));
var body = new
{
surface = "google_search",
query = "best llm observability tools",
@params = new
{
gl = "us",
hl = "en",
page = 1
}
};
using var response = await client.PostAsync(
"https://api.kendr.org/api/v1/query",
new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json")
);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
var payload = await JsonSerializer.DeserializeAsync(stream);
Console.WriteLine(payload.GetProperty("data").ToString());
Console.WriteLine(payload.GetProperty("remaining_credits").GetInt32());
Java HttpClient example
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
String requestBody = """
{
"surface": "google_search",
"query": "best llm observability tools",
"params": {
"gl": "us",
"hl": "en",
"page": 1
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.kendr.org/api/v1/query"))
.header("Authorization", "Bearer " + System.getenv("KENDR_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Go net/http example
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body := map[string]any{
"surface": "google_search",
"query": "best llm observability tools",
"params": map[string]any{
"gl": "us",
"hl": "en",
"page": 1,
},
}
rawBody, err := json.Marshal(body)
if err != nil {
panic(err)
}
req, err := http.NewRequest(http.MethodPost, "https://api.kendr.org/api/v1/query", bytes.NewReader(rawBody))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("KENDR_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var payload map[string]any
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
panic(err)
}
fmt.Println(payload["data"])
fmt.Println(payload["remaining_credits"])
}
Source-included helper clients
This repository includes dependency-light helper clients for JavaScript and Python. Use these from the checked-out source or your internal package registry when you want a thinner call surface around the raw HTTP routes, or call the HTTPS endpoints directly from any other runtime. A package manifest is not evidence of a public npm or PyPI release; verify a registry release independently before using an install command.
JavaScript SDK
import { KendrClient } from './sdk/javascript/index.js';
const client = new KendrClient({
apiKey: process.env.KENDR_API_KEY,
baseUrl: 'https://api.kendr.org'
});
const response = await client.query({
surface: 'google_search',
query: 'best llm observability tools',
params: { gl: 'us', hl: 'en', page: 1 }
});
console.log(response.data);
Python SDK
from kendr import KendrClient
client = KendrClient(
api_key='YOUR_KENDR_API_KEY',
base_url='https://api.kendr.org',
)
response = client.query(
surface='google_search',
query='best llm observability tools',
params={'gl': 'us', 'hl': 'en', 'page': 1},
)
print(response['data'])
Fetch surfaces, packages, and current credits
Use the public catalog to discover what is available, then use the customer dashboard to read the live wallet balance and package state for the authenticated user.
Public catalog
curl https://api.kendr.org/api/catalog
Dashboard balance
curl https://api.kendr.org/api/me/dashboard \ -H "X-Kendr-Session: $KENDR_SESSION"
The dashboard response includes user.credit_balance, packages, api_keys, purchases, ledger, and surfaces.
Create and use API keys
API keys are customer-scoped credentials. They are created after a customer logs in through a browser session, app session, or OAuth bearer token, and the raw key is returned only once.
Create a key
curl https://api.kendr.org/api/me/api-keys \
-X POST \
-H "X-Kendr-Session: $KENDR_SESSION" \
-H "Content-Type: application/json" \
-d '{
"label": "Production"
}'
List keys
curl https://api.kendr.org/api/me/api-keys \ -H "X-Kendr-Session: $KENDR_SESSION"
The create response includes raw_token once. Persist it immediately and use the returned api_key.id later for revocation.
Successful response and errors
Successful query response
{
"ok": true,
"surface": "google_search",
"credits_charged": 1,
"remaining_credits": 249,
"data": {
"results": [],
"answer": "",
"citations": []
},
"normalized": {
"results": [],
"answer": "",
"citations": []
}
}
Errors to handle
- 400: invalid payload, missing surface, unsupported surface, or invalid JSON.
- 401: no valid API key, session, or OAuth bearer token was supplied.
- 402: the wallet does not have enough credits for the requested surface.
- 502: the requested surface could not be completed.
Kendr charges credits only after the request succeeds. Failed attempts do not directly consume credits.
Read the model response envelope
Successful model responses add three Kendr blocks next to the compatibility-format fields. A trimmed /v1/responses result:
{
"id": "0887c41b-5285-409d-ad04-b900b9a258c3",
"model": "kendr-intelligent",
"status": "completed",
"output_text": "Hello! How can I help?",
"usage": { "input_tokens": 36, "output_tokens": 25, "total_tokens": 61 },
"kendr_usage": {
"credits_charged": "0.01165",
"credits_charged_micros": 11650,
"remaining_credits": "49886.925505",
"request_id": "0887c41b-5285-409d-ad04-b900b9a258c3",
"pricing_version": "cloud-v1"
},
"kendr_routing": {
"receipt_id": "kr_86f82eb4d7a54fa2b30d8ce43e7d5f72",
"request_id": "0887c41b-5285-409d-ad04-b900b9a258c3",
"policy_version": "balanced-v1",
"catalog_version": "catalog-2026-08-12",
"pricing_version": "cloud-v1",
"rate_card_version": "2026-08-12T00:00:00Z",
"requested_model": "kendr-intelligent",
"selected_model_alias": "kc-llama-4-scout",
"task_category": "flash",
"reason_code": "trivial_greeting_cheapest_fast",
"confidence": "0.95",
"tools": [],
"latency_ms": 842,
"attempted_candidates": [
{"attempt": 1, "selected_model_alias": "kc-llama-4-scout", "status": "succeeded", "latency_ms": 842}
],
"usage": {
"input_tokens": 36,
"cached_input_tokens": 0,
"cache_write_tokens": 0,
"output_tokens": 25,
"reasoning_tokens": 0,
"total_tokens": 61
},
"cost": {
"currency": "credits",
"credits_charged_micros": 11650,
"credits_charged": "0.01165"
}
},
"kendr_optimization": {
"enabled": true,
"requested_mode": "balanced",
"resolved_mode": "balanced",
"engine": "kendr_optimizer",
"estimated_input_tokens_avoided": 24,
"estimated_credits_saved_micros": 4000,
"estimated_credits_without_optimization_micros": 15650,
"shadow": false
}
}
| Block | What it tells you |
|---|---|
| kendr_usage | Exact settlement for this request: credits charged (also as integer microcredits), remaining wallet balance, token counts, and the idempotent request_id. Retrying with the same idempotency key replays this settled response instead of charging again. |
| kendr_routing | Present on Kendr managed routing requests. It identifies the requested route product, selected public alias, decision category/reason/confidence, sanitized attempt and fallback outcomes, latency, policy/catalog/pricing versions, settled token or billing-unit usage, and settled credit cost. Store receipt_id for support or audit correlation, but treat it as opaque and never parse its current kr_... form. Provider identities, router-model internals, and raw provider errors are not public. |
| kendr_optimization | Optimizer receipt: requested and resolved mode, engine, compression ratio, estimated input tokens avoided, estimated credits saved, comparable unoptimized cost, quality checks, and fallback state. shadow: true is preview-only; resolved_mode: "off" means the request was sent unmodified. |
A failed managed-route request can return sanitized routing identifiers, versions, selected alias, attempts, and outcome metadata in its error details. It does not claim settled usage or cost when the request was not settled successfully.
Keep API keys out of client code
Store Kendr API keys in a server-side secret manager or protected environment variable. Never place a live key in browser JavaScript, a mobile bundle, a public repository, logs, analytics events, or support screenshots.
# Local development only KENDR_API_KEY=kndr_live_replace_with_your_key # Application configuration KENDR_BASE_URL=https://api.kendr.org/v1 KENDR_MODEL=kendr-intelligent
- Create separate keys for development and production so one environment can be revoked without interrupting the other.
- Use the narrowest scopes your integration needs. Model listing needs models:read; generation needs models:invoke; usage reads need usage:read.
- Rotate a key by creating its replacement, updating callers, confirming traffic on the new key, and then revoking the old key.
- Proxy browser and mobile requests through your backend when your product cannot safely hold a server credential.
Kendr configuration reference
Use environment variables or protected deployment secrets for service configuration. Client applications normally need only KENDR_API_KEY, KENDR_BASE_URL, and the selected model alias.
| Configuration | Purpose |
|---|---|
| KENDR_API_KEY | Server-side API key used by your integration. |
| KENDR_BASE_URL | Client SDK base URL, usually https://api.kendr.org/v1. |
| KENDR_MODEL | Default model alias, for example kendr-intelligent. |
| KENDR_PUBLIC_BASE_URL | Public origin used in generated links and callbacks. |
| KENDR_ALLOWED_ORIGINS | Comma-separated origins allowed to call browser-facing JSON routes. |
| KENDR_DATABASE_URL or DATABASE_URL | PostgreSQL connection string. |
| KENDR_REDIS_URL | Redis connection string for shared runtime state. |
| KENDR_INTERNAL_JWT_SECRET | Secret used to authenticate Kendr service-to-service calls. |
| KENDR_CREDENTIAL_ENCRYPTION_KEY | Encryption key for stored connector and MCP credentials. |
| KENDR_USER_REQUESTS_PER_MINUTE | Per-user model request rate limit. The implementation default is 60 when Redis-backed rate-limit state is available; the same limit is enforced per replica from in-process state when that shared state is unavailable. Deployment and account limits can differ, and a 429 response is authoritative. |
| KENDR_SES_FROM_EMAIL, KENDR_SES_REGION | Email sender and region for sign-in messages. |
| AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN | AWS credentials when the deployment uses AWS-backed capabilities. |
| AWS_REGION, AWS_DEFAULT_REGION | Default AWS region for general AWS SDK calls. |
| BEDROCK_MODEL_REGION | Region used for Bedrock model invocation. This can differ from the default AWS region. |
| Managed web-search gateway | Cross-provider web search is configured on the Kendr side through the managed gateway; deployments enable it during setup and applications need no additional configuration. |
| OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, XAI_API_KEY | Provider credentials for native model routes. |
| DEEPSEEK_API_KEY, ZAI_API_KEY, QWEN_API_KEY or DASHSCOPE_API_KEY, BYTEPLUS_API_KEY or ARK_API_KEY, KIMI_API_KEY, MISTRAL_API_KEY, SARVAM_API_KEY, TOKENRA_API_KEY | Additional provider credentials for enabled native routes. QWEN_API_KEY and BYTEPLUS_API_KEY take precedence over their provider-native fallback names. |
| KENDR_FREE_ACCOUNT_CREDITS, KENDR_REFERRAL_REWARD_CREDITS, KENDR_REFERRAL_JOIN_BONUS_CREDITS, KENDR_PROMOTIONAL_CREDIT_EXPIRATION_DAYS | Promotional grant amounts and the default promotional-grant expiry window. The older KENDR_CREDIT_EXPIRATION_DAYS name remains a compatibility fallback. Neither setting applies to completed paid purchases; purchased credit lots have no expiry. |
| RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET | Payment provider credentials for credit purchases. |
Retries, timeouts, and idempotency
Idempotency-Key is optional — Kendr generates one per request when absent. Supplying your own key is recommended for production retry safety: reuse the same key only when retrying the same logical operation, and create a new key for a new user request.
| Status | What it means | Recommended action |
|---|---|---|
| 400 | The request is invalid or uses an unsupported field. | Do not retry unchanged. Validate the body against OpenAPI and correct it. |
| 401 / 403 | The key is missing, invalid, revoked, or lacks the required scope. | Do not loop. Check the credential and scopes. |
| 402 | The shared wallet cannot cover the request. | Ask the account owner to add credits, then retry as a new attempt. |
| 408 / 429 | The call timed out or traffic is being limited. | Retry with exponential backoff and jitter. Honor Retry-After if a gateway includes it, but do not require that header. |
| 500 / 502 / 503 | The request could not be completed by the service or selected route. | Retry a small number of times with the same idempotency key, then surface a recoverable error. |
Model calls can take longer than normal JSON APIs, especially for intelligent routing or tool use. Before the first streamed answer delta, a disconnect may cancel the request. After output starts, Kendr finishes and settles the server-owned request. To recover without any chance of starting a second generation, send an authenticated, empty-body POST /api/v1/llm/responses/replay with the original Idempotency-Key. This actor- and wallet-scoped lookup never plans, reserves credits, or invokes a provider, and a settled replay retains its optimizer and web-search receipts. Compatibility clients may instead resend the exact original body and key with X-Kendr-Idempotency-Replay-Only: true.
Handle streaming correctly
- Send stream: true and keep the HTTP connection open for server-sent events.
- Parse complete SSE events rather than treating arbitrary network chunks as complete JSON messages.
- Handle start, status, delta, done, and error events where present.
- Accumulate text deltas for display, but treat the terminal event as the authoritative completion and usage record.
- If the connection fails before completion, call the empty-body POST /api/v1/llm/responses/replay with the original idempotency key. That lookup can never start another generation.
- Do not assume every model emits identical timing or intermediate events; depend only on the compatible event fields you use.
Track usage without recording sensitive prompts
Record your own request identifier, idempotency key, selected Kendr alias, status, latency, token totals, and returned credit charge. Avoid logging authorization headers or full prompts unless your own privacy policy explicitly requires and protects that data.
Account usage
curl https://api.kendr.org/api/me/billing/summary \ -H "X-Kendr-Session: $KENDR_SESSION"
Available models
curl https://api.kendr.org/v1/models \ -H "Authorization: Bearer $KENDR_API_KEY"
Build authenticated model selection from GET /v1/models at runtime. Use unauthenticated GET /api/public/models for public directories or pre-login discovery; its versioned response exposes public capabilities, availability, and credit rates but deliberately omits private provider routes and account-specific aliases. An alias can become temporarily unavailable, so avoid hardcoding assumptions about every account or environment.
Production checklist
- Create a production key with only the scopes the service requires and store it outside source control.
- Use TLS and the exact https://api.kendr.org API base domain.
- Generate an idempotency key for every chargeable operation and preserve it across retries.
- Handle 400, 401, 402, 429, and 5xx responses deliberately; cap retry attempts.
- Set connect, read, and overall deadlines appropriate to model latency.
- Read the model catalog at runtime and provide a graceful fallback when the preferred alias is unavailable.
- Monitor latency, failures, token usage, and credits charged without logging secrets.
- Test key rotation, low-credit behavior, duplicate retries, streaming disconnects, and revoked credentials before launch.