Developer docs

Kopik bases, in your code.

Every public or unlisted knowledge base can also be queried outside the website: through a REST API for your apps, or through an MCP server for your AI agents. Same base, same price, same cited answers. And your own bases, private ones included, are a free RAG backend for your agents, with nothing to host.

Overview: REST or MCP?

🤖 MCP for agents

Claude (Desktop, Code), Cursor, ChatGPT, your own agent: any MCP-compatible client. The agent discovers the tools itself, decides when to query the base and cites its sources. Nothing to code: a URL and a key are all you need.

🧩 REST for your apps

Your website, back office, chatbot or scripts. One HTTP call returns a written answer with its passages, or just the passages if you'd rather feed them to your own model.

Bases are listed in the catalogue. Each base has an identifier, its slug, visible in its URL: kopik.io/b/<slug>. Its page also provides ready-to-copy snippets.

Ask in any language: answers come back in the language of the question, whatever the language of the base's documents. Before searching, a language model expands the question into keywords and synonyms in the documents' language; the full-text search itself is tuned to that language (language field, also shown on each base's page). The 8 most relevant passages are kept, and answers are written only from them.

Your own private RAG. Make a base private in your dashboard and query it with your own API key, over REST or MCP: only you can reach it, and your questions to your own bases are free (fair use: 200 a day). Upload documents, get an endpoint. No vector database, ingestion pipeline or server to run.

Authentication

Create an API key from your dashboard. It starts with kpk_ and is shown only once: treat it like a password. Keep it server-side, never in code that runs in a browser. Send it in the Authorization header:

HTTP header
Authorization: Bearer kpk_…

The x-api-key: kpk_… header is also accepted. Keys are stored hashed: if you lose one, revoke it and create another. The catalogue (listing and reading bases) is public and needs no key; only questions do. A revoked key stops working immediately. Your key also opens your own private bases, so keep it secret.

Billing

  • Each base has its own price per question, set by its creator and given in priceCents (euro cents).
  • The price is charged to your Kopik credit, topped up by card from the dashboard. The same credit works on the website, the API and MCP.
  • A question with no result is not charged: if the base has nothing relevant, or the answer fails for a technical reason, the charge is cancelled.
  • Cap the price with maxPriceCents: if the base costs more, the call is refused (409, price_above_max) and nothing is charged. Recommended for agents running in a loop, since a creator can change their price at any time.
  • Insufficient credit → 402 error with code insufficient_credit; nothing is charged.
  • The 1 free question a month applies to the website only: the API and MCP are always billed.
  • Your own bases are free, through every channel: public, unlisted or private (fair use: 200 questions a day).
  • The answer and passages modes cost the same.
  • Credit is topped up by card (packs of €10, €25, €50 or €100), never expires and is non-refundable.

REST API

Base URL: https://kopik.io/api/v1. JSON bodies and responses (UTF-8).

GET

/bases

Public · free

Lists public, non-empty bases, most queried first. Optional parameters: topic, q (keyword matched against title and description), limit (100 max). Unlisted bases are not included, but can still be queried by their slug.

Topics: legal, hr, tax, finance, environment, health, aviation, agriculture, industry, real-estate, tech, marketing, education, other.

Request
curl "https://kopik.io/api/v1/bases?topic=legal&q=leave"
200 response
{
  "bases": [
    {
      "slug": "employment-law-leave-x7k2",
      "title": "Employment law: Leave",
      "description": "Paid leave, family-event leave, public holidays…",
      "topic": "legal",
      "topicLabel": "Legal",
      "language": "english",
      "priceCents": 10,
      "documents": 12,
      "passages": 840,
      "questions": 1375,
      "url": "https://kopik.io/b/employment-law-leave-x7k2",
      "updatedAt": "2026-09-20T08:14:00.000Z"
    }
  ]
}
GET

/bases/{slug}

Public · free

Details of a public or unlisted base, including its document language. A private base returns 404 here, but its owner can still query it with their key.

Request
curl https://kopik.io/api/v1/bases/employment-law-leave-x7k2
200 response
{
  "slug": "employment-law-leave-x7k2",
  "title": "Employment law: Leave",
  "description": "Paid leave, family-event leave, public holidays…",
  "topic": "legal",
  "topicLabel": "Legal",
  "language": "english",
  "priceCents": 10,
  "documents": 12,
  "passages": 840,
  "questions": 1375,
  "url": "https://kopik.io/b/employment-law-leave-x7k2",
  "updatedAt": "2026-09-20T08:14:00.000Z"
}
POST

/bases/{slug}/query

🔑 Key required · billed

Asks the base a question. Works on public and unlisted bases, and on your own private bases. Body: question (3 to 2,000 characters), optional maxPriceCents (price ceiling in euro cents) and mode:

  • "answer" (default): a written answer drawn from the base, with [n] references to the passages;
  • "passages": only the most relevant passages (answer is null), so you can reason with your own model.
Request (answer mode)
curl -X POST https://kopik.io/api/v1/bases/employment-law-leave-x7k2/query \
  -H "Authorization: Bearer kpk_…" \
  -H "Content-Type: application/json" \
  -d '{"question": "How many days of leave do I get for my wedding?", "mode": "answer", "maxPriceCents": 20}'
200 response (answer mode)
{
  "id": "cm1x0q2ab0001kopik",
  "answer": "An employee is entitled to **4 days of leave** for their wedding or civil partnership [1]. This leave does not reduce their pay [2].",
  "passages": [
    {
      "n": 1,
      "document": "Employment code (family-event leave).pdf",
      "text": "On presentation of supporting documents, the employee is entitled to leave: 1° For their wedding or civil partnership: four days…",
      "score": 0.82
    },
    {
      "n": 2,
      "document": "Employment code (family-event leave).pdf",
      "text": "These days of absence do not lead to any reduction in pay…",
      "score": 0.61
    }
  ],
  "costCents": 10,
  "balanceCents": 2490
}
Request (passages mode)
curl -X POST https://kopik.io/api/v1/bases/employment-law-leave-x7k2/query \
  -H "Authorization: Bearer kpk_…" \
  -H "Content-Type: application/json" \
  -d '{"question": "wedding leave", "mode": "passages"}'
200 response (passages mode)
{
  "id": "cm1x0q9cd0002kopik",
  "answer": null,
  "passages": [
    { "n": 1, "document": "…", "text": "…", "score": 0.82 }
  ],
  "costCents": 10,
  "balanceCents": 2480
}

If nothing is found, passages is empty, costCents is 0 and, in answer mode, the answer says so in plain words. balanceCents is your balance after the question.

Errors

Every error returns an HTTP status and a JSON body with a human-readable message and a stable, machine-readable code:

402
{
  "error": "Insufficient credit: this base costs €0.10 per question. Top up at kopik.io/dashboard.",
  "code": "insufficient_credit"
}

Branch on code, never on the message: messages may be reworded, codes never change.

codeHTTPMeaningWhat to do
invalid_json400The body is not valid JSON.Send a JSON body with Content-Type: application/json.
question_too_short400Empty question, or under 3 characters.Ask a real question.
question_too_long400Question over 2,000 characters.Shorten the question.
api_key_missing401No API key in the request.Add Authorization: Bearer kpk_….
api_key_invalid401Unknown or revoked key.Check the key, or create a new one in the dashboard.
insufficient_credit402Your credit doesn't cover the base's price.Top up from the dashboard.
base_not_found404No base with this slug, or the base is private.Check the slug (visible in the base's URL).
base_empty409The base has no documents yet.Try again once its creator has added documents.
price_above_max409The base costs more than your `maxPriceCents`. Nothing is charged.Raise your ceiling if the new price suits you, or pick another base.
refused422The question cannot be answered.Rephrase the question.
rate_limited429Too many requests (see Limits).Wait (honour Retry-After when present), then retry with backoff.
unexpected500Unexpected error on our side.Retry; nothing is charged on failure.
answer_failed502The answer could not be generated.Retry; you were not charged.
not_configured503The answer engine is temporarily unavailable.Retry later.

Error message language

Messages are in English by default. Send x-kopik-locale: fr (or en) to choose; without it, an Accept-Language header starting with fr gets French. The code is the same in every language.

curl
curl -X POST https://kopik.io/api/v1/bases/employment-law-leave-x7k2/query \
  -H "Authorization: Bearer kpk_…" \
  -H "x-kopik-locale: en" \
  -H "Content-Type: application/json" \
  -d '{"question": "How many days of leave do I get for my wedding?"}'

MCP server

Kopik exposes an MCP server (“Streamable HTTP” transport, stateless) that works with Claude, Cursor, ChatGPT and any MCP client. Tool names, descriptions and results are in English. Two URLs, depending on what you want to give your agent:

The whole catalogue

https://kopik.io/api/mcp

The agent finds the right base itself.

  • list_bases { topic?, query? } (free)
  • ask_base { base, question }
  • search_base { base, question }

A single base

https://kopik.io/api/mcp?base=<slug>

Pre-scoped tools: the agent can only query this base. Each base's page shows its own URL. Works for your private bases too, with your key.

  • ask_base { question }
  • search_base { question }

Tools

list_bases{ topic?, query? }
Lists public bases, filterable by topic or keyword. Free. Catalogue URL only.
ask_base{ base, question, maxPriceCents? }
Written answer followed by numbered source passages.
search_base{ base, question, maxPriceCents? }
Relevant passages only, without a written answer. Same price as ask_base.

On a single-base URL, base is omitted: ask_base { question } and search_base { question }. The optional maxPriceCents refuses the call, without charge, if the base costs more.

Each call is billed at the base's price, exactly like the API, and ends with the cost and remaining balance. The key goes in the Authorization: Bearer kpk_… header; only list_bases works without one. JSON-RPC batches are limited to 10 requests.

Prompt-injection protection. Text that comes from a base is returned between <kopik-untrusted> tags, preceded by a note telling the agent to treat it strictly as reference data and never to follow instructions it contains.

Claude Code

The whole catalogue
claude mcp add --transport http kopik https://kopik.io/api/mcp --header "Authorization: Bearer kpk_…"
A single base
claude mcp add --transport http kopik-employment-law-leave-x7k2 "https://kopik.io/api/mcp?base=employment-law-leave-x7k2" --header "Authorization: Bearer kpk_…"

Keep the quotes around the URL: in zsh (the default shell on macOS), an unquoted ? is a filename pattern and the command fails.

Other clients (Cursor, Claude Desktop, …)

Most clients read an mcpServers config file (in Cursor: .cursor/mcp.json). The common format, with a URL and headers:

mcp.json
{
  "mcpServers": {
    "kopik": {
      "url": "https://kopik.io/api/mcp",
      "headers": { "Authorization": "Bearer kpk_…" }
    },
    "kopik-employment-law-leave-x7k2": {
      "url": "https://kopik.io/api/mcp?base=employment-law-leave-x7k2",
      "headers": { "Authorization": "Bearer kpk_…" }
    }
  }
}

Limits & safety

Limits protect the service and your credit. Going over one returns 429 with code rate_limited.

WhatLimit
Questions per user (all channels)20 per minute · 1,000 per day
Your questions to your own basesFree · fair use of 200 per day
Requests per IP address (REST and MCP)120 per minute, then 429 with Retry-After: 60
MCP JSON-RPC batch10 requests max
Knowledge bases per account20
Per base1,000 documents · 20 million characters
Per document4 MB per file · 2 million characters · 1,500 PDF pages
Uploads120 per hour

Safety for agents: set maxPriceCents so a price change never costs a looping agent more than planned; branch on the error code; and treat base content as data (over MCP it arrives wrapped in <kopik-untrusted> tags). Answers are written from the base's passages only, but they can still be wrong: they aren't legal, medical or financial advice.

Examples

JavaScript (fetch, Node 18+)

ask.mjs
const res = await fetch("https://kopik.io/api/v1/bases/employment-law-leave-x7k2/query", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.KOPIK_API_KEY}`,
    "Content-Type": "application/json",
    "x-kopik-locale": "en",
  },
  body: JSON.stringify({
    question: "How many days of leave do I get for my wedding?",
    mode: "answer",
  }),
});

const data = await res.json();
if (!res.ok) {
  // data.code is stable: branch on it (e.g. "insufficient_credit", "rate_limited").
  throw new Error(`Kopik ${res.status} ${data.code}: ${data.error}`);
}

console.log(data.answer);
for (const p of data.passages) console.log(`[${p.n}] ${p.document}`);
console.log(`Cost: €${(data.costCents / 100).toFixed(2)} · balance: €${(data.balanceCents / 100).toFixed(2)}`);

Python (requests)

ask.py
import os
import requests

res = requests.post(
    "https://kopik.io/api/v1/bases/employment-law-leave-x7k2/query",
    headers={
        "Authorization": f"Bearer {os.environ['KOPIK_API_KEY']}",
        "x-kopik-locale": "en",
    },
    json={"question": "How many days of leave do I get for my wedding?", "mode": "answer"},
    timeout=60,
)
data = res.json()
if not res.ok:
    # data["code"] is stable: branch on it (e.g. "insufficient_credit", "rate_limited").
    raise RuntimeError(f"Kopik {res.status_code} {data['code']}: {data['error']}")

print(data["answer"])
for p in data["passages"]:
    print(f"[{p['n']}] {p['document']}")
print(f"Cost: €{data['costCents'] / 100:.2f} · balance: €{data['balanceCents'] / 100:.2f}")

An answer can take about twenty seconds: use a timeout of at least 60 seconds, and retry after a pause on 429 (rate_limited).

Ready to plug in a base?

Create a key and top up your credit, or start with a private base of your own, free to query.

Create an API key