Developer & API

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.

Base URL: https://api.kendr.org OpenAI and Anthropic compatible Works with your harness Streaming and web search 5% model-cost markup

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.

  1. 1
    Set your key
    export KENDR_API_KEY="kndr_live_..." on macOS/Linux, or $env:KENDR_API_KEY="kndr_live_..." in PowerShell.
  2. 2
    Send a chat completion
    Use kendr-intelligent for request-by-request routing, or replace it with an available alias returned by GET /v1/models.
  3. 3
    Keep the receipt
    Read 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.

Base URL rule

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
Scope note

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.

RoutePurpose
GET /api/public/modelsPublic, unauthenticated catalog for crawlable model pages and pre-login discovery. It omits private routes and account-specific aliases.
GET /v1/modelsList currently available Kendr model aliases.
POST /v1/responsesGenerate with the OpenAI Responses shape, including streaming, tools, and optional web search.
POST /v1/chat/completionsUse OpenAI-compatible chat clients, SSE chunks, and optional web search.
POST /v1/messagesUse the Anthropic Messages request and response shape, including optional streaming.
POST /v1/messages/count_tokensFree Anthropic-compatible token estimate for a message request.
POST /v1/video/analysesQueue an account-scoped asynchronous video analysis.
GET /v1/video/analyses/{id}Poll video analysis state and result.
GET /api/v1/openapi.jsonDownload the model-only OpenAPI contract.
GET|POST /api/me/ai/preferencesRead 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.

ProductAliasRouting poolContext behavior
Kendr Intelligentkendr-intelligentGeneralOptimized
Kendr Intelligent Directkendr-intelligent-directGeneralOriginal context
Kendr Coderkendr-coderCoding-focusedOptimized
Kendr Coder Directkendr-coder-directCoding-focusedOriginal context
The alias is the contract

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.

FieldRequiredUse
modelYesKendr alias such as kendr-intelligent or a Normal text model alias from GET /v1/models.
input or messagesYesPrompt text or role-based chat messages, depending on the route format.
instructionsNoSystem-level guidance for Responses-compatible requests.
max_output_tokens or max_tokensNoCaps generated tokens for the selected route.
streamNoSet true to receive server-sent events.
web_searchNoTri-state control: omit for automatic selection from the current request, use true to request search, or false to prohibit it.
toolsNoTool 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_idNoStable 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_idNoBody-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_formatNoStructured-output preference for routes that support it.
metadataNoApplication metadata. metadata.require_web_search makes search mandatory; metadata.intelligent_reroute selects a model again instead of reusing a compatible conversation route.

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.

CredentialWho configures itWhere it is sentPurpose
Kendr API key
kndr_live_...
Customer or customer applicationAuthorization or X-API-KeyCalls model, query, usage, and other API-key-enabled Kendr routes.
Kendr session or OAuth tokenKendr login flowCookie, X-Kendr-Session, or bearer headerActs for a signed-in user and can create or revoke Kendr API keys.
Connected-service authorizationThe signed-in customerKendr connection flow onlyAllows an explicitly connected app or MCP server to provide data and tools. It is never returned through model API responses.
Do not proxy vendor secrets from your client

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."}]
  }'
Streaming behavior

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.

SettingBeforeWith Kendr
API keyYour provider keyKENDR_API_KEY (kndr_live_...)
Base URLProvider defaulthttps://api.kendr.org/v1
ModelProvider model IDkendr-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);
Compatibility boundary

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

1
Discover surfaces and packages
Call GET /api/catalog to see the current surfaces, packages, and docs metadata.
2
Authenticate
Use an API key for backend code, or use X-Kendr-Session or OAuth when the request is directly tied to a customer session.
3
Fetch credits and wallet state
Call GET /api/me/dashboard to read the credit balance, packages, purchases, ledger, and enabled surfaces.
4
Run a query
Send the surface key, query string, and optional params to POST /api/v1/query.

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"
    }
  }'
Switch languages

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.

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);

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"
Returned by the dashboard

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"
Important

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.
Charging rule

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
  }
}
BlockWhat it tells you
kendr_usageExact 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_routingPresent 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_optimizationOptimizer 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.
Receipt behavior on errors

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.

ConfigurationPurpose
KENDR_API_KEYServer-side API key used by your integration.
KENDR_BASE_URLClient SDK base URL, usually https://api.kendr.org/v1.
KENDR_MODELDefault model alias, for example kendr-intelligent.
KENDR_PUBLIC_BASE_URLPublic origin used in generated links and callbacks.
KENDR_ALLOWED_ORIGINSComma-separated origins allowed to call browser-facing JSON routes.
KENDR_DATABASE_URL or DATABASE_URLPostgreSQL connection string.
KENDR_REDIS_URLRedis connection string for shared runtime state.
KENDR_INTERNAL_JWT_SECRETSecret used to authenticate Kendr service-to-service calls.
KENDR_CREDENTIAL_ENCRYPTION_KEYEncryption key for stored connector and MCP credentials.
KENDR_USER_REQUESTS_PER_MINUTEPer-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_REGIONEmail sender and region for sign-in messages.
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKENAWS credentials when the deployment uses AWS-backed capabilities.
AWS_REGION, AWS_DEFAULT_REGIONDefault AWS region for general AWS SDK calls.
BEDROCK_MODEL_REGIONRegion used for Bedrock model invocation. This can differ from the default AWS region.
Managed web-search gatewayCross-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_KEYProvider 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_KEYAdditional 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_DAYSPromotional 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_SECRETPayment 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.

StatusWhat it meansRecommended action
400The request is invalid or uses an unsupported field.Do not retry unchanged. Validate the body against OpenAPI and correct it.
401 / 403The key is missing, invalid, revoked, or lacks the required scope.Do not loop. Check the credential and scopes.
402The shared wallet cannot cover the request.Ask the account owner to add credits, then retry as a new attempt.
408 / 429The 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 / 503The 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.
Timeout budget

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.