Korely

Platform

Data & governance

Where your agents' memory lives, who can touch it, and how to erase it.

EU data residency

Your stored memory lives on our own infrastructure in the EU. It is stored in the EU, never replicated to the US. EU data residency applies on every tier, including the free Hobby plan, and requires no configuration on your part.

Storage is in the EU, and so are the embeddings: an open embedding model (EmbeddingGemma) runs on our server in Helsinki and turns memories, facts and search queries into vectors there. The language model that reads a memory into facts depends on the project's region: the Gemini API (Google, in the United States and other countries where Google has facilities, under the EU Standard Contractual Clauses and a data processing agreement) in the default global region, gpt-oss-120b on Scaleway in Paris in the eu region. Either way it sees text: the memory you write, with the end user's identifier and up to ten earlier memories of the same end user (300 characters each), to extract its facts; the earlier facts a new one is compared with, for the contradiction check; and a new relation phrase, with a value it was stated with and examples of the relations already known, the first time that end user's vocabulary meets it; and the subject, relation and object of a fact you write directly or correct, for the same checks. A search or context query never reaches a language model. Neither provider trains its models on that text. The derived outputs, facts, embeddings and graph edges, are stored only in your account's namespace, in the EU. The full list is on the Security page.

EU residency applies to every namespace by construction, your facts, embeddings, and graph edges are written only to EU-hosted storage. A GET /v1/ping probe confirms the API is reachable and echoes your key's tier, scopes, the region label of the install (region, eu-hel1 on the Cloud, set by Korely to its Helsinki server) and the region of its project (processing_region):

Terminal window
curl https://api.korely.ai/v1/ping \
-H "Authorization: Bearer kor_live_..."
# {"ok":true,"tier":"hobby","region":"eu-hel1","processing_region":"global","scopes":["memories:read","memories:write"]}

Read-only and write-only keys

A key reads and writes unless you choose otherwise. On the API Keys page of the dashboard, under What it may do in Create API key, tick only memories:read (search, context, lists and the audit log: a frontend or a support tool) or only memories:write (add, change and forget: an import pipeline that never reads the memory back). The key list then shows a Read only or Write only badge. A call outside the key's scopes is refused and reads or writes nothing: 403 forbidden on REST, an error: forbidden, ... message from the MCP tool. A key keeps the scopes it was made with: for different ones, make a new key and revoke the old.

Regions

Each project has a region. You choose it on the Projects page of the dashboard, when you create a project or later. An account made with POST /v1/agents/init has no dashboard login: it picks the region of its Default project at signup, with korely init --agent --processing-region eu or "processing_region": "eu" in the body (Sign up as an agent), or later, once you connect its key to a login.

RegionModel that reads your memoriesWhere it runs
global (default)GeminiGoogle: United States and other countries where Google has facilities
eugpt-oss-120bScaleway, Paris, France
  • Storage and embeddings are in the EU in both regions.
  • A project with no region chosen is global, the Default project included. On the Cloud the Projects page offers the two regions above. On an install of your own, a project with no region follows the install's default writer.
  • In the eu region no memory text reaches Google. When the EU model is unavailable, new memories are stored at once and their facts follow when it is back. They are never sent to the global region.
  • A change of region applies to the next writes, including memories still waiting for their facts. Facts already written stay as they are.
  • The region says where your memories are read, not where they are stored. GET /v1/ping reports both: region is a label the operator of the install sets (on the Cloud, eu-hel1, the Helsinki server that stores the data; on Self-hosted, the REGION value in .env, which the service does not check), processing_region the region of the key's project.

Namespaces & scoping

Every /v1 call is scoped by your account and by the project of the key first, then by the namespace parameters you pass:

  • user_id, your end user. The unit of isolation; unlimited on every tier. Choose any opaque string (a database ID, a hashed email, a UUID) of 1 to 255 characters, without / or control characters, other than . or .., and not beginning with erased: (the prefix of the pseudonyms an erasure leaves): the erase call addresses it as one URL path segment. Other values are refused with 422 invalid_request.
  • agent_id, one of your apps or agents. Plans cap the number of distinct agent IDs (Hobby: 2, Developer: 10, Team: 100, Scale: 500). Passing a new agent_id past your plan's limit returns 429 agent_cap_exceeded (with limit and used in its detail), and nothing is written.
  • run_id, a single session or conversation. Stored on the memory; search and list can filter on it. Facts are not scoped by run.

On /v1 the project wall is enforced server-side on every query; the one exception is a key with the admin scope on the /admin API (see Projects). Within a project, pass user_id to keep a read to one end user, or give a frontend a key confined to its own end users: a read with no user_id covers every end user of the project. The GET /v1/users endpoint lists every user_id held in the key's project, so you always know exactly which end-user data you hold.

Terminal window
# List every end user in the key's project, with memory and fact counts
curl https://api.korely.ai/v1/users \
-H "Authorization: Bearer kor_live_..."
# {"users": [
# {"user_id": "customer-4812", "memories": 18, "facts": 9, "last_active": "2026-06-15T..."},
# {"user_id": "customer-7091", "memories": 4, "facts": 2, "last_active": "2026-06-14T..."}
# ], "total": 2}

Erasure

Erase one end user in the key's project, every memory and every fact in the live database, in a single call (one call per project):

Terminal window
curl -X DELETE "https://api.korely.ai/v1/users/customer-4812/memories" \
-H "Authorization: Bearer kor_live_..."
# 200 OK
# {"user_id":"customer-4812","memories_deleted":18,"facts_deleted":9,
# "erasure":"permanent","audit_id":"aud_..."}

The call returns a receipt, memories_deleted, facts_deleted, erasure: "permanent" and an audit_id, so the erasure is itself provable. Erasure is not the bi-temporal supersede: every memory and every typed fact about the user, superseded ones included, is physically deleted, so no query surface (REST, SDK, CLI, or MCP) and no include_invalidated flag can bring it back. What stays is the audit record, with the counts and a keyed pseudonym of the user_id (never the id itself or the erased content; GET /v1/audit?user_id= with the same id still finds it), for your own Article 5(2) accountability. The endpoint is idempotent: erasing an unknown or already-erased user returns 200 with zero counts, never an error.

The live rows are deleted at once. Encrypted backups drop their copy within 35 days (see the Privacy Policy), and a restore makes the erasure again.

To forget a single memory (rather than an entire user), use DELETE /v1/memories/{id}, or korely.delete(id) in the SDK. It returns {"id":"mem_...","status":"forgotten","facts_invalidated":N,"audit_id":"aud_..."}. A repeat delete of the same memory, or a delete of an id that was never created, returns 404 not_found. This is not an erasure: the memory leaves every read, but its row stays, and the facts only it stated are closed and stay as history (include_invalidated still shows them); a fact another memory also states stays current. When a person asks for their data to be gone, use the end-user erasure above, which deletes the rows. To erase one memory for good instead, call DELETE /v1/memories/{id}?erase=true: the row, its text and vector, and the facts only it stated are deleted, with an audit record. A memory forgotten earlier can be erased the same way.

If you track which user_id values map to real people in your own system, a one-call erasure is the Korely side of an erasure request workflow, no support ticket, no waiting period.

API keys

Requests authenticate with a kor_live_ key in the Authorization: Bearer header. Korely stores only a SHA-256 hash of the key, the plaintext is shown exactly once, at creation, and cannot be retrieved afterward. If a key is lost or suspected compromised, revoke it in the dashboard and create a new one; the old key stops working instantly across every surface (REST, SDK, CLI, MCP).

An invalid or missing key returns 401 invalid_key. Every Korely error carries code (a stable slug to branch on) and message at the top level, next to detail, which holds the original sentence, the list of fields that failed validation, or, on refusals that carry numbers such as quota_exceeded, those numbers. One catch block handles all of them:

{
"detail": "Invalid or missing API key: no live key matches it (a revoked key stops at once); your keys are at https://agent.korely.ai/keys",
"code": "invalid_key",
"message": "Invalid or missing API key: no live key matches it (a revoked key stops at once); your keys are at https://agent.korely.ai/keys"
}

Best practice: store your key in an environment variable (KORELY_API_KEY) rather than hard-coding it. The SDK reads this variable automatically. Never commit a kor_live_ key to source control.

No training on your data

Your stored memories and facts are never used to train or fine-tune any model, Korely's own or any third party's. Embeddings are computed on our own server. The rest of the AI inference on the write path (fact extraction, relation typing) runs through sub-processors covered by a data processing agreement, in data-processor mode, where they are contractually prohibited from using your data for their own model training. The processed outputs belong exclusively to your account namespace.

There is no opt-out toggle because there is no opt-in: training on customer data is not part of the data pipeline, by design.

Quota & rate limits

Plans carry monthly write and query quotas (see pricing), each with a 10% grace on top. A write that would take you past the plan's figure (with the month's top-ups) plus the grace returns 429 quota_exceeded and writes nothing; a read past the queries quota gets the same answer. There is no surprise overage charge: on a paid plan you can top up the month from the dashboard's Usage page, or upgrade at any time. The quota 429 carries no Retry-After header; its detail says the month's figure, top-ups included (limit), what the month has used (used) and when the count starts again (resets_at, 00:00 UTC on the 1st):

{
"detail": {
"code": "quota_exceeded",
"message": "Monthly write limit reached: 250 writes on the Hobby plan this month, plus a 10% grace. It starts again on 2026-11-01. To go on now, move to a paid plan at https://agent.korely.ai/usage (an account made with korely init signs in there first and adopts its key)",
"limit": 250,
"used": 275,
"resets_at": "2026-11-01T00:00:00Z"
},
"code": "quota_exceeded",
"message": "Monthly write limit reached: 250 writes on the Hobby plan this month, plus a 10% grace. It starts again on 2026-11-01. To go on now, move to a paid plan at https://agent.korely.ai/usage (an account made with korely init signs in there first and adopts its key)"
}

Short-term request bursts are handled separately. If you exceed the per-minute, per-hour or per-day rate limit, you get a 429 that does carry a Retry-After header, an integer number of seconds to wait before retrying.

For the full security posture, encryption in transit, database access, sub-processor list, and compliance documentation, see the security page.

Related