ker-ai docs

API reference

The ker-ai Malayalam voice API in full — authentication, session configuration, REST endpoints, the WebSocket voice protocol, the tool-call loop, errors, and rate limits.

This is the primary integration surface for ker-ai. Everything the service does is reachable here over REST and a WebSocket.

Base URL & authentication

Your invite includes a base URL (e.g. https://api.kerai.io) and an API key. Authenticate every request with a bearer token:

Authorization: Bearer <YOUR_API_KEY>

For the WebSocket, pass the same key as a bearer token on the upgrade request (or as documented for your client). Keep keys server-side; never ship them in a public bundle.

Session configuration

Every voice session is described by a config object, validated on both client and server.

Required

FieldTypeNotes
modestringclient_text or server_audio.
languagestringBCP-47, e.g. ml-IN.
llmProviderstringLanguage-model provider id.
llmModelstringModel id within that provider.
debugbooleanEmit verbose diagnostics.

Optional

voice, sttProvider, sttModel, ttsProvider, ttsModel, streamResponse, temperature, systemPromptOverride, tools[].

{
  "mode": "server_audio",
  "language": "ml-IN",
  "llmProvider": "gemini",
  "llmModel": "gemini-2.5-flash",
  "ttsProvider": "azure",
  "voice": "ml-IN-SobhanaNeural",
  "debug": false,
  "tools": []
}

REST endpoints

GET /health

Liveness probe. Returns 200 with a small JSON body when the service is up.

{ "status": "ok" }

GET /api/providers/catalog

The speech, language, and voice providers and models available to your key, so a client can offer a picker without hardcoding ids.

{
  "llm": [{ "provider": "gemini", "models": ["gemini-2.5-flash"] }],
  "stt": [{ "provider": "sarvam", "models": ["saarika:v2"] }],
  "tts": [{ "provider": "azure", "voices": ["ml-IN-SobhanaNeural"] }]
}

WebSocket voice protocol

The live channel is a WebSocket at /ws/voice. The client streams microphone audio (or text) up; the service streams transcripts, assistant text, synthesized audio, and tool calls back. Every message is { "type": "...", "payload": { ... } }.

Client → server

{ "type": "session.init",    "payload": { /* session config */ } }
{ "type": "audio.chunk",     "payload": { "base64": "...", "mime": "audio/webm" } }
{ "type": "function.result", "payload": { "id": "...", "result": { } } }

Server → client

{ "type": "transcript.partial", "payload": { "text": "..." } }
{ "type": "transcript.final",   "payload": { "text": "..." } }
{ "type": "assistant.text",     "payload": { "text": "..." } }
{ "type": "assistant.audio",    "payload": { "base64": "...", "mime": "audio/mpeg" } }
{ "type": "function.call",      "payload": { "id": "...", "name": "...", "arguments": { } } }
{ "type": "error",              "payload": { "message": "..." } }

The tool-call loop

  1. The server emits function.call with an id, the tool name, and parsed arguments.
  2. The client runs the matching handler and replies with function.result carrying the same id.
  3. The server folds the result into the model and continues — usually with assistant.text and, in server_audio, assistant.audio.

Utterance-based chunking is fine: send a complete spoken turn as audio chunks, then let the server transcribe and respond. Continuous low-latency streaming is not required for a good experience.

Errors

Failures surface two ways:

  • REST — a non-2xx status with a JSON { "message": "..." } body. 401 means a missing or invalid key; 429 means you hit a rate limit.
  • WebSocket — an error event ({ "type": "error", "payload": { "message" } }) for problems mid-session (unsupported language/voice, malformed audio, provider timeout). The socket may close after a fatal error; reconnect and re-session.init.

Rate limits

Each plan sets a request rate (per minute) and a cap on concurrent sessions; over either, requests are rejected with 429. The limits for your plan are listed on the pricing comparison and returned in your account.

On this page