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_ofinstead 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 kindsession), 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 timefrom korely_memory import Korely
korely = Korely() # reads KORELY_API_KEY from the environmentmem = 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 secondsprint(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
--jsonoutput 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).
pip install korely-memory # Python 3.9 or laterexport KORELY_API_KEY=kor_live_...python -c "from korely_memory import Korely; print(Korely().ping().tier)"# hobbyThe fields of the answer:
tier: your plan.region(eu-hel1): where your data is stored, Korely's servers in Helsinki.processing_region:euorglobal, set per project. New projects start asglobal.processing_regionpicks the extraction model. Withglobal(the default) it is a model from Google, outside the EU; witheuit 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 timefrom 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) # processingwhile 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.
| Operation | REST | Python SDK | Node.js SDK | CLI | MCP |
|---|---|---|---|---|---|
| Context block | GET /v1/context | get_context() | getContext() | korely context | korely_get_context |
| Search memories | POST /v1/memories/search | search() | search() | korely search | korely_search |
| Read a memory by id | GET /v1/memories/{id} | get() | get() | korely get | No |
| Read facts | GET /v1/facts | get_facts() | getFacts() | korely facts | korely_get_facts |
| Add a memory | POST /v1/memories | add() | add() | korely add | korely_add |
| Batch import | POST /v1/batch | batch() | batch() | korely batch | No |
Facts true on a date (as_of) | Yes | Yes | Yes | --as-of | Yes |
What Korely knew on a date (known_as_of) | Yes | Yes | Yes | --known-as-of | Yes |
| Declare a name for an end user (declared name) | POST /v1/users/{id}/names | declare_name() | declareName() | korely init --name | No |
Leave out session logs (exclude_sessions) | Yes | Yes | Yes | --exclude-sessions | Yes |
| Correct or close a fact | PATCH /v1/facts/{id}, POST /v1/facts/{id}/forget | correct_fact(), forget_fact() | correctFact(), forgetFact() | korely correct-fact, korely forget-fact | korely_correct_fact |
| Forget one memory | DELETE /v1/memories/{id} | delete() | delete() | korely delete | korely_forget_memory |
| Erase one memory for good | DELETE /v1/memories/{id}?erase=true | No | No | No | korely_forget_memory with erase=true |
| Erase everything about one end user | DELETE /v1/users/{id}/memories | delete_all() | deleteAll() | korely delete-all --user-id ... --yes | No |
| Manage projects and keys | Admin key only | Admin key only: projects(), keys() | No | Admin key only: korely projects, korely keys | No |
- 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--namefor 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 (a500, a429, a503, 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. A403is 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.
GET /v1/memories,get_all()/getAll(),korely list: list memories.PATCH /v1/memories/{id},update(),korely update: change a memory's text.GET /v1/memories/{id}/history,history(),korely history: the history of one memory.GET /v1/events,events(),korely events: which writes finished processing.POST /v1/facts,add_fact_triple()/addFactTriple(),korely add-fact: write a fact directly.GET /v1/profile,get_profile()/getProfile(),korely profile: one end user's facts, grouped by family.GET /v1/batch/{id},batch_status()/batchStatus(),korely batch-status: the status of a batch.GET /v1/users,users(),korely users: end users with stored data.GET /v1/agents,list_agents()/listAgents(),korely agents: agent names and the agent limit.DELETE /v1/agents/{id},delete_agent()/deleteAgent(),korely delete-agent: erase one agent's data.GET /v1/audit,audit()anditer_audit()/iterAudit(),korely audit: the audit log.DELETE /v1/account?confirm=<account id>,delete_account()/deleteAccount(),korely delete-account: close an account created withPOST /v1/agents/init; an unconfined key of that account with theadminpermission (every init key since 1.0.5) closes the whole account.
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.
| Status | Code | SDK exception | When it happens |
|---|---|---|---|
400 | confirmation_required | KorelyError, before sending | DELETE /v1/account with no confirm, or one that is not the account's id (true included, from 1.0.23). The SDKs refuse it themselves before sending, with the same code. |
401 | invalid_key | AuthenticationError | Missing, malformed, revoked or expired API key. |
403 | forbidden | NamespaceForbiddenError (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. |
404 | not_found | NotFoundError | The id does not exist in this project, or the memory was forgotten. |
409 | stale_write | StaleWriteError | You passed expected_updated_at, the updated_at you last read, to update(), and the memory changed since. The guard stops you overwriting another edit. |
409 | fact_not_current | ConflictError | Correcting a fact that is already history. current_fact_id names the current one. |
409 | account_has_login | ConflictError | DELETE /v1/account on an account you sign in to. |
422 | invalid_request | APIError | The request failed validation, or a body field is unknown. The message names the field, such as "content: Field required". |
429 | quota_exceeded | QuotaExceededError | You 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. |
429 | rate_limit_exceeded | QuotaExceededError | Too many requests this minute, hour or day. Retry-After gives the seconds to wait, exposed as retry_after / retryAfter. |
429 | agent_cap_exceeded | QuotaExceededError | A write with a new agent_id would pass your plan's agent limit. detail carries limit and used. No Retry-After. |
500 | internal_error | APIError | The request failed on our side. |
503 | search_unavailable, model_unavailable, unavailable | APIError | A 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. |
503 | service_paused | APIError | Korely 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.
| Plan | Price | Write quota | Query quota | Agent limit | Requests per minute / hour / day |
|---|---|---|---|---|---|
| Hobby | Free | 250 writes/mo | 25,000 queries/mo | 2 agents | 60 / 1,000 / 10,000 |
| Developer | €19/mo + VAT | 2,000 writes/mo | 250,000 queries/mo | 10 agents | 300 / 20,000 / 200,000 |
| Team | €79/mo + VAT | 8,000 writes/mo | 1,000,000 queries/mo | 100 agents | 1,000 / 100,000 / 1,000,000 |
| Scale | €249/mo + VAT | 25,000 writes/mo | 10,000,000 queries/mo | 500 agents | 3,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
429if 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
429until 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.warningevent 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
- SDK guide: every method, with examples.
- CLI guide: every command and flag, and coding-assistant setup.
- MCP server: setup and the six tools.
- API reference: every REST call.
- Memory model: how
user_id,agent_idandrun_idscope memory.