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 | Portal and browser-driven flows after /api/auth/login or /api/auth/register. |
| 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 create and refresh the kendr_session cookie. They are useful for portals, browser automation, and any flow that wants cookie-backed auth rather than manually managing X-Kendr-Session.
Register and store the cookie
curl https://kendr.org/api/auth/register \
-X POST \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
"email": "user@example.com",
"password": "supersecret123",
"full_name": "Example User"
}'
Log in and refresh the cookie
curl https://kendr.org/api/auth/login \
-X POST \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
"email": "user@example.com",
"password": "supersecret123"
}'
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
email must be a valid address and password must be at least 8 characters on registration.
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.
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"
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.
| 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.4.0",
"category": "feature_request",
"rating": 5,
"message": "Please add saved query presets."
}'
SDK helpers
JavaScript helpers
import {
KendrClient,
loginWithPassword,
registerWithPassword
} from './sdk/javascript/index.js';
const auth = await loginWithPassword({
email: 'user@example.com',
password: 'supersecret123',
baseUrl: 'https://kendr.org'
});
const client = new KendrClient({
sessionToken: auth.session.token,
baseUrl: 'https://kendr.org'
});
const dashboard = await client.getDashboard();
Python helpers
from kendr import KendrClient, login_with_password
auth = login_with_password(
email='user@example.com',
password='supersecret123',
base_url='https://kendr.org',
)
client = KendrClient(
session_token=auth['session']['token'],
base_url='https://kendr.org',
)
dashboard = client.get_dashboard()