Korely

Surfaces

All surfaces

Every surface on this page reads and writes the same Korely memory, so you can pick one per job and mix them.

Korely has six surfaces: the REST API, the SDKs, the CLI, the MCP server, the hooks for coding assistants, and the dashboard for people. Each API key belongs to one project, and surfaces that use keys of the same project see the same memories.

This page is for developers choosing how to connect an app or an agent to Korely.

Terms

The Glossary defines the words used across the docs. These are the ones this page relies on.

  • Surface: one way to reach Korely, such as the REST API, an SDK, the CLI or the MCP server.
  • Hook: a command a coding assistant runs by itself at set moments, such as the start and the end of a session.
  • Dashboard: the web app at agent.korely.ai where you manage projects, keys and webhooks.
  • Base URL: the address that each call's path is added to.
  • known_as_of: a date that makes a read return what Korely knew on that day, before it learned of later changes. as_of instead asks what was true in the world on that day.
  • exclude_sessions: set to true, a read leaves out the session logs that coding assistants save (memories of kind session), and the facts that come only from those logs.
  • Batch import: one call that saves up to 500 memories.

Quick start

Install the Python SDK with pip install korely-memory (Python 3.9 or later), then get a free API key in the Quickstart and run export KORELY_API_KEY=kor_live_.... The code saves one memory for an end user, waits for extraction, and prints the context block for a question.

import time
from korely_memory import Korely
korely = Korely() # reads KORELY_API_KEY from the environment
mem = korely.add("Prefers invoices as PDF.", user_id="test-user-1")
while korely.get(mem.id).status == "processing":
time.sleep(2) # extraction usually takes about ten seconds
print(korely.get_context("invoice preferences", user_id="test-user-1").context)

Under ## Known facts the block lists the facts, such as a preference for PDF. Under ## Relevant memories it shows the memory. A model writes the facts, so their wording varies.

If the block shows the memory but no facts, print korely.get(mem.id).status. Add a memory explains each status and what to do on error.

Which surface should I use?

  • You build a product with memory per customer: the Python or Node.js SDK.
  • You use another language, or want plain HTTP: the REST API.
  • You script a cron job, a CI step or a shell pipeline: the CLI. It has --json output and needs no code. It comes with the Python package.
  • An AI assistant should call Korely as a tool, such as Claude Code or Cursor: the MCP server.
  • A coding assistant should save every session without asking: the hooks, set up per assistant in Claude Code, Codex or Cursor.
  • A person wants to look at memory, correct a fact or manage keys: the dashboard.

Install and connect

With curl, call https://api.korely.ai/v1/.... The SDKs and the CLI add /v1 themselves: their base URL is https://api.korely.ai. The MCP server is at https://api.korely.ai/agent/mcp.

Create a key on the API Keys page of the dashboard. Every call sends it in one header, Authorization: Bearer kor_live_.... Only POST /v1/agents/init needs no key: your code calls it to create a free account and its key, without the dashboard (how an agent signs up).

Terminal window
pip install korely-memory # Python 3.9 or later
export KORELY_API_KEY=kor_live_...
python -c "from korely_memory import Korely; print(Korely().ping().tier)"
# hobby

The fields of the answer:

  • tier: your plan.
  • region (eu-hel1): where your data is stored, Korely's servers in Helsinki.
  • processing_region: eu or global, set per project. New projects start as global.
  • processing_region picks the extraction model. With global (the default) it is a model from Google, outside the EU; with eu it is Scaleway in Paris. The two can extract facts differently. Data governance names both.
  • scopes: the key's permissions.

A 401 with invalid_key means the key is wrong, revoked or expired.

Run the Node.js examples as an ES module, such as a .mjs file: they use top-level await.

The same flow on three surfaces

A support agent saves what a customer said, recalls it in the next conversation, then erases the customer on request. It uses one agent_id and one end user. Run it with KORELY_API_KEY set. Its last step erases what it saved. The curl version also needs jq, a command-line JSON tool.

import time
from korely_memory import Korely
korely = Korely()
# Conversation 1: store what the customer said.
memory = korely.add(
"Prefers invoices as PDF, replies fastest before 10am CET",
user_id="customer-4812",
agent_id="support-bot",
metadata={"source": "support-chat"},
)
print(memory.status) # processing
while korely.get(memory.id).status == "processing":
time.sleep(2) # usually about ten seconds
# Conversation 2: build the context block before calling your model.
ctx = korely.get_context(
"billing preferences",
user_id="customer-4812",
agent_id="support-bot",
token_budget=400,
)
messages = [
{"role": "system", "content": f"You are a helpful billing assistant.\n\n{ctx.context}"},
{"role": "user", "content": "Can you send me the invoice?"},
]
# Under "## Known facts" the block lists facts such as
# "The user prefers PDF (since <the day of the write>)".
# The customer asks to be forgotten: erase this end user.
receipt = korely.delete_all(user_id="customer-4812")
print(receipt.memories_deleted, receipt.facts_deleted, receipt.audit_id)
# For example: 1 1 aud_3d0f5e2a... (memories erased, facts erased, receipt id)

The receipt's audit_id is its id in the audit log.

Reference

What each surface can do

No surface writes answers for you: reads return memories and facts, and your own model writes the reply.

OperationRESTPython SDKNode.js SDKCLIMCP
Context blockGET /v1/contextget_context()getContext()korely contextkorely_get_context
Search memoriesPOST /v1/memories/searchsearch()search()korely searchkorely_search
Read a memory by idGET /v1/memories/{id}get()get()korely getNo
Read factsGET /v1/factsget_facts()getFacts()korely factskorely_get_facts
Add a memoryPOST /v1/memoriesadd()add()korely addkorely_add
Batch importPOST /v1/batchbatch()batch()korely batchNo
Facts true on a date (as_of)YesYesYes--as-ofYes
What Korely knew on a date (known_as_of)YesYesYes--known-as-ofYes
Declare a name for an end user (declared name)POST /v1/users/{id}/namesdeclare_name()declareName()korely init --nameNo
Leave out session logs (exclude_sessions)YesYesYes--exclude-sessionsYes
Correct or close a factPATCH /v1/facts/{id}, POST /v1/facts/{id}/forgetcorrect_fact(), forget_fact()correctFact(), forgetFact()korely correct-fact, korely forget-factkorely_correct_fact
Forget one memoryDELETE /v1/memories/{id}delete()delete()korely deletekorely_forget_memory
Erase one memory for goodDELETE /v1/memories/{id}?erase=trueNoNoNokorely_forget_memory with erase=true
Erase everything about one end userDELETE /v1/users/{id}/memoriesdelete_all()deleteAll()korely delete-all --user-id ... --yesNo
Manage projects and keysAdmin key onlyAdmin key only: projects(), keys()NoAdmin key only: korely projects, korely keysNo
  • Forget, erase: forgetting hides a memory from every read and closes the facts only it stated. Erasing removes it for good. Only REST and MCP erase one memory; the MCP tool forgets unless you pass erase=true. See delete, forget and erase.
  • Declared names on the CLI: there is no separate command. Run korely init --name "Maria Rossi" --user-id maria, repeating --name for each name.
  • Projects and keys: the dashboard manages them with your login. An admin key manages them outside it. Never give an admin key to a tool your agent can call.
  • Dashboard: it also shows usage, sells the monthly top-up of writes, and lets you correct or forget a fact.
  • When the dashboard cannot reach the server (since 1.0.22). Only a 401, the server saying the session is over, signs you out. Any other failure of the check the dashboard makes when it opens (a 500, a 429, a 503, a service that is restarting) keeps your session: the page says why the session could not be checked, with Try again and Sign out. On the Cloud, an account that no longer exists also ends the session, and the sign-in page says so. A refusal that names a wait (Retry-After) is shown as "Try again after 14:32 (in 12 minutes)" on your clock, and Try again comes back at that moment. A 403 is shown as a refusal, with no Try again, because asking again would not change it. A page that stops while drawing leaves a note in its place and the rest of the dashboard keeps working; the Memory Graph says so when the browser has no WebGL 2.

Coding assistants: hooks and the MCP server

A hook gives a coding assistant the repository's context block when a session starts, and saves the session as a memory of kind session when it ends.

korely init --tool claude-code (or codex, cursor) installs these hooks and korely-mcp, a local MCP server with four tools: korely_get_context, korely_add, korely_search, korely_get_facts. It runs on your machine and calls the Cloud with your key.

For Claude Code, Codex or Cursor, run korely init --tool. For any other agent or MCP client, connect the hosted server at https://api.korely.ai/agent/mcp: it has the six tools of the MCP column.

Other calls

These exist on REST, both SDKs and the CLI; each link opens the call's reference.

Python options after the first argument are keyword-only. Node.js takes an options object with snake_case keys, such as deleteAll({ user_id }). The SDK guide and the CLI guide list every signature and flag.

Error codes

Every error answer carries a stable code and a readable message: branch on code, which each SDK exception also carries. StaleWriteError is a kind of ConflictError.

The CLI prints error: <message> [<code>] on stderr and exits with 1, or 2 for a wrong command line. With --json it prints an object with code, message and status. On MCP, the same refusals come back as tool errors.

StatusCodeSDK exceptionWhen it happens
400confirmation_requiredKorelyError, before sendingDELETE /v1/account without confirm=true. The SDKs refuse it themselves, with the same code.
401invalid_keyAuthenticationErrorMissing, malformed, revoked or expired API key.
403forbiddenNamespaceForbiddenError (the 403 class)The key lacks the permission the call needs (memories:read or memories:write), or it is a confined key and the call names another end user.
404not_foundNotFoundErrorThe id does not exist in this project, or the memory was forgotten.
409stale_writeStaleWriteErrorYou passed expected_updated_at, the updated_at you last read, to update(), and the memory changed since. The guard stops you overwriting another edit.
409fact_not_currentConflictErrorCorrecting a fact that is already history. current_fact_id names the current one.
409account_has_loginConflictErrorDELETE /v1/account on an account you sign in to.
422invalid_requestAPIErrorThe request failed validation, or a body field is unknown. The message names the field, such as "content: Field required".
429quota_exceededQuotaExceededErrorYou used your monthly writes or queries, plus 10% of grace. Wait until resets_at (00:00 UTC on the 1st, in the error's detail) or upgrade. No Retry-After.
429rate_limit_exceededQuotaExceededErrorToo many requests this minute, hour or day. Retry-After gives the seconds to wait, exposed as retry_after / retryAfter.
429agent_cap_exceededQuotaExceededErrorA write with a new agent_id would pass your plan's agent limit. detail carries limit and used. No Retry-After.
500internal_errorAPIErrorThe request failed on our side.
503search_unavailable, model_unavailable, unavailableAPIErrorA part of the service did not answer: the search, the model or the database. Nothing was written. Retry, after the seconds in Retry-After when the answer has one.
503service_pausedAPIErrorKorely paused its own model calls to cap spending, for example when a daily model budget runs out. Memories are still stored and get their facts later. Fact writes and corrections answer this code with Retry-After: 3600 (one hour). So do searches: the pause also stops the embedding of the question.

Limits and pricing

Four plans. The prices in this table are in euro, and Pricing shows them where you are. Quotas reset monthly. End users (user_id values) are unlimited on every plan. Every plan includes facts.

PlanPriceWrite quotaQuery quotaAgent limitRequests per minute / hour / day
HobbyFree250 writes/mo25,000 queries/mo2 agents60 / 1,000 / 10,000
Developer€19/mo + VAT2,000 writes/mo250,000 queries/mo10 agents300 / 20,000 / 200,000
Team€79/mo + VAT8,000 writes/mo1,000,000 queries/mo100 agents1,000 / 100,000 / 1,000,000
Scale€249/mo + VAT25,000 writes/mo10,000,000 queries/mo500 agents3,000 / 300,000 / 3,000,000
  • The quotas count writes (a long memory counts several) and reads. A batch counts the writes of all its memories at once, and is refused with a 429 if they do not fit in what is left of the month.
  • The rate limits count every call of the account, from every surface.
  • There is no overage billing. Past the quota plus 10%, writes or queries answer 429 until the month rolls over or you upgrade.
  • On a paid plan, a top-up adds 1,000 writes to the current month for €10, once a month, from the dashboard.
  • At 80% of the monthly write quota, a quota.warning event goes to the webhook endpoints registered on the Webhooks page of the dashboard.

Details

Use them together

For example, an app writes with the SDK, a nightly cron job adds a digest with the CLI, and a coding assistant reads through MCP. With keys of one project and the same user_id, each sees what the others wrote.

What a read costs

Reads do not call a language model. Search and get_context turn the question into an embedding on our own server. Each read still counts as one query of the month and toward the rate limit. The model runs on the write, to extract facts and check contradictions (Architecture). A read without user_id covers every end user of the key's project.

See also