OpenAI-compatible API
Your node speaks the chat-completions dialect every client already knows. Point a base URL and a key at it and the tools you have open keep working, unchanged.
Answers are grounded in your knowledge base
This is not a plain model proxy. Every request searches the documents on your node first. The reply is then shaped by material the caller never mentioned, and the passages it used come back with the answer. That is the point of the endpoint, and it is the one behaviour that will surprise you if you expect a pass-through.
What you need
Base URL
Your node's address on the mesh, with
/v1on the end. The console shows it under a node's API tab.http://<your-node>:8080/v1 API key
Issue one from the API keys tab. It is shown once and stored only as a hash, so save it when you create it.
kvk_...The node lives on your private mesh
This address does not resolve on the public internet. A client has to be on the same tailnet to reach it. That is what keeps the endpoint off the open web, and you do not have to configure anything for it.
Quick start
curl
# Nothing Kovalent-specific in the request
curl http://<your-node>:8080/v1/chat/completions \
-H "Authorization: Bearer kvk_your-key-here" \
-H "Content-Type: application/json" \
-d '{
"model": "kovalent",
"messages": [{"role": "user", "content": "What is our retention policy?"}]
}'Python
# pip install openai
from openai import OpenAI
client = OpenAI(
base_url="http://<your-node>:8080/v1",
api_key="kvk_your-key-here",
)
completion = client.chat.completions.create(
model="kovalent",
messages=[{"role": "user", "content": "What is our retention policy?"}],
)
print(completion.choices[0].message.content)Node
# npm install openai
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "http://<your-node>:8080/v1",
apiKey: "kvk_your-key-here",
});
const completion = await client.chat.completions.create({
model: "kovalent",
messages: [{ role: "user", content: "What is our retention policy?" }],
});
console.log(completion.choices[0].message.content);Streaming
Server-sent events, terminated the way the official clients expect, so the usual streaming loop works without special handling.
stream = client.chat.completions.create(
model="kovalent",
messages=[{"role": "user", "content": "Summarise the handbook."}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")Editors
Any editor client that accepts an OpenAI-compatible provider works. Configure it as
openai with your node as the API base. Continue, in ~/.continue/config.json:# ~/.continue/config.json
{
"models": [
{
"title": "Kovalent",
"provider": "openai",
"model": "kovalent",
"apiBase": "http://<your-node>:8080/v1",
"apiKey": "kvk_your-key-here"
}
]
}Cursor, Zed, and LibreChat take the same two fields under different names. There is no Kovalent plugin to install for any of them.
Citations
The passages an answer was grounded in come back in a
kovalent field alongside the standard response. Strict clients ignore it; a client that knows about it keeps the provenance. On a stream it arrives on the first chunk, before the text it annotates.# Response
{
"id": "chatcmpl-...",
"object": "chat.completion",
"choices": [
{ "index": 0, "message": { "role": "assistant", "content": "Seven years." } }
],
"kovalent": {
"grounded": true,
"citations": [
{
"index": 1,
"content": "Audit records are retained for seven years.",
"metadata": { "source": "handbook.pdf", "page": 4 }
}
]
}
}For clients that render only the message body, send
kovalent_citations: "inline" in the request and the sources are appended to the content instead.Citations and reranking are a Pro feature
Lower tiers get the same grounded answer without the attribution, which matches how every other query path on your node behaves.
What is served
| Endpoint | Notes |
|---|---|
POST /v1/chat/completions | Streaming and non-streaming. Grounded in your knowledge base. |
GET /v1/models | The one model this node actually serves. |
GET /v1/me | What your key grants. Useful for checking a key works. |
Not served yet
Tool and function calling, embeddings, images, and audio. Point an agent framework at this endpoint and tool calls are the first thing it will reach for, so check this list before you build on it.
When something is wrong
| Status | Means |
|---|---|
| 401 | The key is unknown, revoked, or expired. All three look identical on purpose, so the endpoint cannot be used to work out which keys exist. |
| 403 | The key is valid but lacks the scope. Chat needs the chat scope. |
| 503 | The node has no model runtime provisioned, so it refuses rather than answering from somewhere else. |
Errors use the same envelope the OpenAI SDKs parse, so they surface as normal typed exceptions rather than as an unhandled response.
To check a key without writing any code, paste it into the API tab in the console. It calls your node with the key and reports what came back.