Pick the auth flow that matches your client.
Kendr supports several auth modes because the clients are different: browser pages, installed apps, server-side integrations, Kendr Desktop, and the CLI do not all authenticate the same way. This page covers the exact routes, request bodies, and token shapes accepted by https://kendr.org.
Auth matrix
| Mode | Header or state | Best for |
|---|---|---|
| Browser session | kendr_session cookie | Browser-driven flows after /api/auth/otp/verify. |
| App session | X-Kendr-Session | Installed apps that authenticate directly with Kendr and immediately call customer endpoints. |
| API key | Authorization: Bearer kndr_live_... or X-API-Key | Server-to-server usage, background jobs, and long-lived backend integrations. |
| OAuth bearer | Authorization: Bearer kndr_oat_... | Kendr Desktop, CLI, or other native clients using PKCE or device code with the app scope. |
Browser session auth
The browser endpoints separate sign-in from signup, send a six-digit code through AWS SES, and create the kendr_session cookie after verification. They are useful for portals, browser automation, and any flow that wants cookie-backed auth rather than manually managing X-Kendr-Session.
Request a login code
curl https://kendr.org/api/auth/otp/request \
-X POST \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"purpose": "login"
}'
Request signup verification
curl https://kendr.org/api/auth/otp/request \
-X POST \
-H "Content-Type: application/json" \
-d '{
"email": "new@example.com",
"purpose": "signup"
}'
Verify login and store the cookie
curl https://kendr.org/api/auth/otp/verify \
-X POST \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
"email": "user@example.com",
"code": "123456",
"purpose": "login"
}'
Verify and create the account
curl https://kendr.org/api/auth/otp/verify \
-X POST \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
"email": "new@example.com",
"code": "123456",
"purpose": "signup",
"full_name": "Example User",
"terms_accepted": true,
"referral_code": "OPTIONAL"
}'
Inspect browser session state
curl https://kendr.org/api/auth/session \ -b cookies.txt
Log out
curl https://kendr.org/api/auth/logout \ -X POST \ -b cookies.txt \ -c cookies.txt
purpose=login requires an existing account. purpose=signup requires a new email plus full name and legal consent during verification. Omit purpose only for backward-compatible native clients that intentionally combine login and signup. Codes expire after 10 minutes.
App session auth
The app auth routes return a session token directly in the JSON response. That token is sent on later calls as X-Kendr-Session. Installed clients should prefer /api/app/auth/otp/request and /api/app/auth/otp/verify, or OAuth PKCE/device code. Password login remains available for existing clients.
Use the tabs to view the same login flow in Curl, JavaScript, Python, .NET, Java, or Go. The response shape stays the same across every client.
Authenticate and receive the session token
Post credentials and read session.token from the JSON response
curl https://kendr.org/api/app/auth/login \
-X POST \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "supersecret123"
}'
JavaScript fetch example
const response = await fetch('https://kendr.org/api/app/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'supersecret123'
})
});
const payload = await response.json();
const sessionToken = payload.session.token;
Python requests example
import requests
payload = requests.post(
'https://kendr.org/api/app/auth/login',
json={
'email': 'user@example.com',
'password': 'supersecret123',
},
timeout=30,
).json()
session_token = payload['session']['token']
.NET HttpClient example
using System.Net.Http.Json;
using System.Text.Json;
using var client = new HttpClient();
using var response = await client.PostAsJsonAsync(
"https://kendr.org/api/app/auth/login",
new
{
email = "user@example.com",
password = "supersecret123"
}
);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
var payload = await JsonSerializer.DeserializeAsync(stream);
var sessionToken = payload.GetProperty("session").GetProperty("token").GetString();
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 = """
{
"email": "user@example.com",
"password": "supersecret123"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://kendr.org/api/app/auth/login"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
String responseBody = response.body();
System.out.println(responseBody); // Parse session.token with your JSON library.
Go net/http example
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
rawBody, err := json.Marshal(map[string]string{
"email": "user@example.com",
"password": "supersecret123",
})
if err != nil {
panic(err)
}
req, err := http.NewRequest(http.MethodPost, "https://kendr.org/api/app/auth/login", bytes.NewReader(rawBody))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var payload struct {
Session struct {
Token string `json:"token"`
} `json:"session"`
}
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
panic(err)
}
fmt.Println(payload.Session.Token)
}
Successful response
{
"ok": true,
"authenticated": true,
"user": {
"id": 12,
"email": "user@example.com",
"full_name": "Example User",
"credit_balance": 250
},
"session": {
"token": "SESSION_TOKEN",
"header_name": "X-Kendr-Session",
"expires_at": "2026-04-24T12:00:00Z"
}
}
Inspect the app session
curl https://kendr.org/api/app/auth/session \ -H "X-Kendr-Session: $KENDR_SESSION"
Log out the app session
curl https://kendr.org/api/app/auth/logout \ -X POST \ -H "X-Kendr-Session: $KENDR_SESSION"
Create, scope, use, and rotate API keys
A Kendr API key is the caller credential for backend services. It is different from the OpenAI, Anthropic, Bedrock, Gemini, or other upstream provider credential. Your code receives only the kndr_live_... value and calls Kendr model aliases; upstream secrets and provider model IDs remain behind the gateway.
Create a least-privilege key
curl https://kendr.org/api/me/api-keys \
-X POST \
-H "X-Kendr-Session: $KENDR_SESSION" \
-H "Content-Type: application/json" \
-d '{
"label": "Production model service",
"environment": "production",
"scopes": ["models:read", "models:invoke", "usage:read"]
}'
Use the returned key
export KENDR_API_KEY="kndr_live_..." curl https://kendr.org/v1/models \ -H "Authorization: Bearer $KENDR_API_KEY" # X-API-Key is also accepted curl https://kendr.org/v1/models \ -H "X-API-Key: $KENDR_API_KEY"
| Scope | Allows |
|---|---|
| models:read | Discover enabled Kendr model aliases and poll owned video analyses. |
| models:invoke | Call Responses, Chat Completions, Messages, and video analysis creation. |
| connectors:read | Read connector-backed resources where the endpoint supports API-key auth. |
| connectors:invoke | Execute enabled unified query surfaces. |
| usage:read | Read wallet and exact model usage summaries. |
| knowledge:read | Read permitted knowledge resources. |
| knowledge:write | Create or modify permitted knowledge resources. |
List keys without revealing secrets
curl https://kendr.org/api/me/api-keys \ -H "X-Kendr-Session: $KENDR_SESSION"
Revoke during rotation
curl https://kendr.org/api/me/api-keys/revoke \
-X POST \
-H "X-Kendr-Session: $KENDR_SESSION" \
-H "Content-Type: application/json" \
-d '{"api_key_id": 42}'
The create response returns raw_token once. Store it in a server-side secret manager or environment variable, create a replacement before rotation, and never embed it in browser JavaScript or a distributed desktop binary.
OAuth PKCE for desktop apps
Kendr’s first-party OAuth clients are seeded as kendr-desktop and kendr-cli. Native clients can use PKCE with GET /oauth/authorize and POST /oauth/token. Kendr Desktop opens the hosted login page, then returns through its native app callback after the browser login succeeds.
| Parameter | Needed for |
|---|---|
| response_type=code | Required on /oauth/authorize. |
| client_id | Use kendr-desktop or another allowed client. |
| redirect_uri | Must match an allowed client redirect URI, including Kendr loopback or custom URI handlers. |
| code_challenge and code_challenge_method=S256 | Required for PKCE. |
| scope | Optional. Defaults to the client’s default scopes. Include offline_access when you need a refresh token. |
https://kendr.org/oauth/authorize?response_type=code&client_id=kendr-desktop&redirect_uri=kendr%3A%2F%2Foauth%2Fcallback&scope=profile%20email%20app%20offline_access&code_challenge=YOUR_S256_CHALLENGE&code_challenge_method=S256&state=opaque-state
curl https://kendr.org/oauth/token \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=kendr-desktop" \ -d "grant_type=authorization_code" \ -d "code=$OAUTH_CODE" \ -d "redirect_uri=kendr://oauth/callback" \ -d "code_verifier=$CODE_VERIFIER"
OAuth device code for the CLI
Device code is the cleanest CLI flow because the terminal never asks the user for their password. The CLI starts the flow, the user approves it in the browser, and the client then exchanges the device code for an access token.
Start the flow
curl https://kendr.org/oauth/device/code \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=kendr-cli" \ -d "scope=profile email app offline_access"
Response fields
{
"device_code": "kndr_odc_...",
"user_code": "ABCD-EFGH",
"verification_uri": "https://kendr.org/oauth/device",
"verification_uri_complete": "https://kendr.org/oauth/device?user_code=ABCD-EFGH",
"expires_in": 900,
"interval": 5,
"scope": "profile email app offline_access"
}
curl https://kendr.org/oauth/token \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=kendr-cli" \ -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \ -d "device_code=$DEVICE_CODE"
Poll no faster than the returned interval. Kendr returns OAuth protocol errors such as authorization_pending, slow_down, or access_denied as appropriate.
OAuth metadata and user info
Discovery metadata
curl https://kendr.org/.well-known/oauth-authorization-server
User info
curl https://kendr.org/oauth/userinfo \ -H "Authorization: Bearer $KENDR_OAUTH_ACCESS_TOKEN"
profile exposes name and preferred username, email exposes the email address, and app allows customer endpoints such as dashboard, API keys, purchases, and query execution.
App telemetry endpoints
Kendr also exposes app-side endpoints for installations, activity, error reports, feedback, and active notifications. These endpoints accept JSON and can carry extra metadata beyond their primary fields.
| Path | Key fields | Response |
|---|---|---|
| POST /api/app/installations | installation_id, platform, app_version, channel, source | status, install_id, total_installs, recorded_at |
| POST /api/app/activity | installation_id, platform, app_version, source | status, activity_date, identity_type, total_active, recorded_at |
| POST /api/app/errors | Required message; optional install, version, platform, error name, error code, details, stack trace, severity, email | status, error_id, created_at |
| POST /api/app/feedback | Required message; optional install, version, platform, category, rating, email | status, feedback_id, created_at |
| GET /api/app/notifications | No body | count and notifications |
curl https://kendr.org/api/app/feedback \
-X POST \
-H "Content-Type: application/json" \
-d '{
"installation_id": "desktop-prod-001",
"platform": "windows",
"app_version": "0.2.0",
"category": "feature_request",
"rating": 5,
"message": "Please add saved query presets."
}'
SDK helpers
JavaScript helpers
import { KendrClient } from './sdk/javascript/index.js';
const client = new KendrClient({
apiKey: process.env.KENDR_API_KEY,
baseUrl: 'https://kendr.org'
});
const models = await client.listModels();
const answer = await client.response({
model: 'kc-intelligent',
input: 'Summarize this change.'
});
Python helpers
import os
from kendr import KendrClient
client = KendrClient(
api_key=os.environ['KENDR_API_KEY'],
base_url='https://kendr.org',
)
models = client.list_models()
answer = client.response({
'model': 'kc-intelligent',
'input': 'Summarize this change.',
})
When the app is acting for a signed-in user, initialize the same clients with sessionToken/session_token or oauthAccessToken/oauth_access_token instead of an API key.