Korely

API Reference

Overview

Call Korely over HTTP: one key in a header, JSON in and out. This page lists the routes and what they share.

Base URL: https://api.korely.ai. Memories and facts are stored on Korely's servers in Helsinki. The project's region decides which model reads their text to write the facts: global (the default) uses a model from Google, outside the EU; eu uses Scaleway in Paris (see Data governance).

Quick start

You need an API key: Quickstart: get a free API key shows how. Replace kor_live_... with yours. The call changes none of your data.

Terminal window
curl https://api.korely.ai/v1/ping \
-H "Authorization: Bearer kor_live_..."

A working key answers 200:

{
"ok": true,
"tier": "hobby",
"region": "eu-hel1",
"processing_region": "global",
"scopes": ["memories:read", "memories:write"]
}

tier is the name of your plan: hobby, developer, team or scale. scopes is the list of the key's permissions (the API calls them scopes).

No 200? A 401 means the key is missing or wrong: copy it again. Any other status is listed in Errors.

Terms

The Glossary defines the words used across the docs. These are the ones this page relies on: Cloud and Self-hosted, SDK, confined key, end user, agent_id, run_id, write, read, rate limit and webhook. This page adds these:

  • Route: one HTTP method and path, such as GET /v1/ping.
  • region and processing_region: in the ping answer, region names the server that answered (eu-hel1, in Helsinki). processing_region is the region of the key's project, eu or global, which decides the model provider that receives your text: Scaleway in Paris for eu, Google (outside the EU) for global.
  • Batch: one POST /v1/batch call with up to 500 memories. Korely queues it, answers 202, and stores the memories in the background.
  • Event: a write's processing state (processing, ready or error), as List events returns it.
  • expected_updated_at: an optional field of a memory PATCH: the updated_at you last read. If the memory changed since, the edit is refused.
  • degraded: true: a GET /v1/context answer with a part missing, such as the memories. degraded_parts names the part.

Reference

Address and keys

All customer routes are under /v1. The dashboard, the web app for your account, has its own administration API (/admin/*), not part of this API.

Every /v1 request carries Authorization: Bearer kor_live_..., except POST /v1/agents/init, which creates an account and its first key (see Sign up as an agent). Create more keys in the dashboard.

Keep the key on your server. A full key (memories:read and memories:write) reads and writes all memory in its project; a read-only key (memories:read) reads it all. A confined key reaches only its end users.

Routes

The route pages below are still to be reviewed. Until then, check details against the Core operations guides, which are more recent: Add, Search, Get context, Update, Delete and Forget a user.

PageRoute
Core memory operations
Add a memoryPOST /v1/memories
Search memoriesPOST /v1/memories/search
List memoriesGET /v1/memories
Get a memoryGET /v1/memories/{memory_id}
Update a memoryPATCH /v1/memories/{memory_id}
Delete a memoryDELETE /v1/memories/{memory_id}
Memory historyGET /v1/memories/{memory_id}/history
Context & facts
Get contextGET /v1/context
Get factsGET /v1/facts
Write a factPOST /v1/facts
Close a factPOST /v1/facts/{fact_id}/forget
Correct a factPATCH /v1/facts/{fact_id}
Get profileGET /v1/profile
Users & agents
List usersGET /v1/users
Forget a userDELETE /v1/users/{end_user}/memories
Declare a namePOST /v1/users/{end_user}/names
List agentsGET /v1/agents
Delete an agentDELETE /v1/agents/{agent_id}
Audit logGET /v1/audit
Delete the accountDELETE /v1/account
Sign up as an agentPOST /v1/agents/init
Batch & status
Batch importPOST /v1/batch
Batch statusGET /v1/batch/{job_id}
List eventsGET /v1/events
PingGET /v1/ping

The interactive playground shows each route's request and response. It reads the OpenAPI file published on this site, which can lag behind the latest release.

Scoping

Three strings that you choose scope a memory. Filters combine with AND. A write needs a user_id or an agent_id (Every write says whose memory it is); run_id is always optional.

FieldIdentifiesExample
user_idAn end user. Unlimited on every plan."customer-4812"
agent_idOne of your apps or agents. Each name counts toward your plan's agents."support-bot"
run_idOne session or conversation."chat-2026-06-17-a"

On writes, user_id and agent_id:

  • take 1 to 255 characters;
  • contain no / and no control characters;
  • are not . or ..;
  • do not start with erased:, which Korely uses to mark erased end users.

run_id takes 1 to 255 characters. In paths, {end_user} is the user_id.

Errors

An error returns its HTTP status and a JSON body with three fields. Branch on code. message is a sentence for people; its wording may change. detail holds the same sentence, except on a 422, where it lists the fields that failed.

{
"detail": "Memory not found",
"code": "not_found",
"message": "Memory not found"
}
StatusCodeWhen
400bad_requestThe body is not JSON in UTF-8.
401invalid_keyThe header is missing or malformed, or no live key matches it: unknown, revoked or expired. Since 1.0.10 the message names the three; they still get one code, so branch on the code, not on the words. The MCP door answers its own 401 invalid_token.
403forbiddenThe key lacks a permission the route needs, or is confined and the request leaves its end users. Since 1.0.10, a key that init made loses admin when its account is connected to a dashboard login, so every /admin route answers it 403.
403account_not_adoptedCloud only, since 1.0.10. The account is on trial: it cannot make keys or revoke its last live key. Connect its key to a dashboard login first.
404not_foundNo such resource or path. A resource of another project also answers 404.
408request_timeoutThe body did not arrive within 30 seconds. See Details.
409stale_writeA PATCH with an expected_updated_at older than the memory: another write landed first. Read it again.
409fact_not_currentPATCH /v1/facts/{fact_id} on a fact no longer current. current_fact_id, at the top of the body, names the current one, or is null.
409conflictPOST /v1/users/{end_user}/names with a name declared for somebody else.
413payload_too_largeThe body is too large. See Details.
422identity_requiredA write (POST /v1/memories, a row of POST /v1/batch, POST /v1/facts) names neither user_id nor agent_id. Nothing is stored and nothing counts against your quota. Why and what to send.
422invalid_requestA missing, malformed or unknown field, malformed JSON, or a NUL byte in a text. message names the first field.
429quota_exceededThe month's writes or reads are used up. detail carries limit, used and resets_at (00:00 UTC on the 1st). On trial (since 1.0.10) the writes are 40 in all and resets_at is null.
429agent_cap_exceededA new agent_id would pass your plan's number of agents. detail carries limit and used.
429rate_limit_exceededToo many requests in the current minute, hour or day. On trial (since 1.0.10) the sixth write in a minute, with a Retry-After that grows.
500internal_errorOur failure. Retry; tell us if it persists.
503unavailableThe server could not finish in time: another request had locked the data, the database did not answer, or the server was busy.
503search_unavailableA search could not embed its question, or waited 20 seconds for its turn. Not an empty result. GET /v1/context answers degraded: true instead.
503model_unavailableA model that a fact write needs gave no usable answer.
503service_pausedCloud only. POST /v1/facts or PATCH /v1/facts/{fact_id} while the daily safety stop is on: it pauses all calls to the models once the day's model budget is used up. Retry after an hour.

The Retry-After header:

  • is sent with rate_limit_exceeded, unavailable (5 seconds), search_unavailable (5 seconds) and service_paused (3600 seconds);
  • is not sent with quota_exceeded, agent_cap_exceeded or model_unavailable.

Rate limits and quotas

The rate limit caps requests per minute, hour and day for your whole account: all keys, REST and MCP share it. Past it you get 429 rate_limit_exceeded; Retry-After says when that window resets. A request the limit refuses is not counted. Every other call counts, GET /v1/ping included.

The monthly quota caps writes and reads, by plan. You can go 10% over the monthly figure; past that, 429 quota_exceeded: wait for resets_at. A quota.warning webhook fires when the month's writes cross 80%.

An add or a PATCH of a memory counts one write per 6,000 characters of text. POST /v1/facts, a PATCH of a fact and declaring a name count one each. A batch counts whole when Korely queues it. If it does not fit in the writes left this month, it is refused.

Hobby allows 60 requests per minute, 1,000 per hour and 10,000 per day; Pricing has every plan.

No response has X-RateLimit-* headers or says how many calls are left.

Versioning

Korely is versioned by release number: the changelog shows the current release and what each one changes.

From 1.0, nothing published is removed without a 2.0: an endpoint, a field or an error code of a 1.x release stays in every 1.x release.

Details

  • No idempotency. A retried write can store the memory twice and count two writes. An Idempotency-Key header is ignored.
  • Pages of results. Lists of memories, facts, users and agents take limit (default 50, at most 200) and offset, and answer total.
  • Slow bodies. A body has 30 seconds to arrive, or 120 for a batch over 2 MiB. Later, 408 request_timeout.
  • Size limits. On the Cloud, a body over 1 MB (80 MB for a batch) gets a 413 with no JSON body, so check the status, not the body. Self-hosted and smaller bodies meet the API's own limit: 413 payload_too_large over 2,097,152 bytes (78,245,152 for a batch), or when the body holds too many separate text values.
  • Waits. A request waits up to 5 seconds for locked data before 503 unavailable; forgetting a user or deleting an agent waits up to 60.
  • Self-hosted. Quotas, the agent cap, quota.warning, sign-up limits, service_paused, POST /v1/agents/init and DELETE /v1/account exist only on the Cloud. The rate limit is off by default.